在 Android 的 Recyclerview 中显示下载进度

Posted

技术标签:

【中文标题】在 Android 的 Recyclerview 中显示下载进度【英文标题】:Show Download progress in a Recyclerview in Android 【发布时间】:2018-08-07 03:56:09 【问题描述】:

我目前正在开发可以下载视频文件的应用程序。我正在使用下载管理器下载文件,一切正常,因为文件正在正确下载,我可以在通知中看到下载进度。现在我想在另一个类(包含recyclerview)中显示那些下载文件的进度。那么我应该怎么做才能实现这个模块呢?

  String urlDownload = mUrl;
        String filename = mUrl.substring(mUrl.lastIndexOf('/') + 1);
        Log.i("onLoadResource()", "url = " + mUrl + "\n" + filename);
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(urlDownload));

    request.setDescription("Testando");
    request.setTitle("Download");
    request.allowScanningByMediaScanner();
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);

    final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);

    final long downloadId = manager.enqueue(request);

    final ProgressBar mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);

    new Thread(new Runnable() 

        @Override
        public void run() 

            boolean downloading = true;

            while (downloading) 

                DownloadManager.Query q = new DownloadManager.Query();
                q.setFilterById(downloadId);

                Cursor cursor = manager.query(q);
                cursor.moveToFirst();
                int bytes_downloaded = cursor.getInt(cursor
                        .getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
                int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));

                if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) 
                    downloading = false;
                

                final int dl_progress = (int) ((double)bytes_downloaded / (double)bytes_total * 100f);

                runOnUiThread(new Runnable() 

                    @Override
                    public void run() 

                        mProgressBar.setProgress((int) dl_progress);

                    
                );


                cursor.close();
            

        
    ).start();

【问题讨论】:

只是将下载百分比广播到该类..并更新该活动中回收者的进度条 好的,我明白了,谢谢,但是如何在 recyclerview 适配器中添加多个文件请给我一些指导 如何在该课程中设置广播下载百分比请指导我 我有这个问题超过 1.5 年。 *** 中没有人回答这个话题。甚至,谷歌也帮不上忙 我遇到了同样的问题。你解决了吗? 【参考方案1】:

在您为每个 RecyclerViewItem 使用的数据模型中维护一个变量用于进度。当您收到有关进度的更新时,请更新您正在使用的数据集,然后调用 notifyItemChanged(mPos)notifyItemRangeChanged() 以通知适配器。 notifyItemChanged(mPos) 触发 onBindVieHolder 对应的位置,即使它当前不可见。

【讨论】:

【参考方案2】:

以下是一些一般性建议。

您可以通过调用notifyItemRangeChanged(int positionStart, int itemCount, Object payload) 来更新RecyclerView 行。这将在您的适配器中调用onBindViewHolder(FeedBaseViewHolder holder, int position, List<Object> payloads),您可以在其中使用进度条更新一行。

在有效负载中,您可以将所需值的角度与您的要求一起传递,例如开始进度操作、更新进度、停止进度。

希望对你有帮助。

【讨论】:

【参考方案3】:

之前建议使用notifyItemChanged() 的答案给了我三个大问题:

    它不必要地更新整个 ViewHolder 和所有子视图, 当您在显示更新的同时滚动列表时,它会产生无穷无尽的烦人动画 它显示随机(不值得/不正确的)ViewHolders 的进度更新

为我解决所有这些问题的方法是使用回调来更新与进度相关的特定视图。我的解决方案是在 Kotlin 中,因为 A) 这就是我所做的,B) 它是为了让这些问题变得简单,C) OP 没有将 Java 指定为语言。

PRDownload 库通过在每个响应/更新时调用的内置回调系统使这更容易一些 - 这是DownloadManager 所没有的。要使用DownloadManager,您只需创建一个计时器(可能是WorkManager)来查询DownloadManager 数据库并定期获取下载进度,并根据进度调用回调。

PRDownload 的依赖:
implementation "com.mindorks.android:prdownloader:0.6.0"

适配器

import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.downloader.Error
import com.downloader.OnDownloadListener
import com.downloader.OnProgressListener
import com.downloader.PRDownloader
import com.downloader.Progress

val mutableMapOfIDToProgressListenerCallback: MutableMap<Int, MyDownloadProgressListener> =
    mutableMapOf()

class RecyclerViewAdapter(private val dataSet: List<ListItem>) :
    RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() 

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) 
        lateinit var listItem: ListItem
        val percentDownloaded = itemView.findViewById<TextView?>(R.id.percent_downloaded)
        fun setUpdateViewLambda() 
            mutableMapOfIDToProgressListenerCallback[listItem.id]?.setLabmda 
                percentDownloaded?.text = toPercent(it).toString()
            
        

        fun removeUpdateViewLambda() 
            mutableMapOfIDToProgressListenerCallback[listItem.id]?.setLabmda 
        

        init 
            view.setOnClickListener  //to demonstrate downloads
                Thread  //use coroutines instead
                        downloadItem(
                            listItem,
                            adapterPosition,
                            this@RecyclerViewAdapter,
                            "https://downloads.gradle-dn.com/distributions/gradle-6.9.1-bin.zip", //insert your download here
                            view.context.getExternalFilesDir(null)!!.absolutePath,
                            "a$listItem.id.zip"
                        )
                .start()
            
        
    

    override fun onViewAttachedToWindow(holder: ViewHolder) 
        holder.setUpdateViewLambda()
        super.onViewAttachedToWindow(holder)
    

    override fun onViewDetachedFromWindow(holder: ViewHolder) 
        holder.removeUpdateViewLambda() //remove callback so that recycled viewholder doesn't undeservingly get updates
        super.onViewDetachedFromWindow(holder)
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 
        return ViewHolder(
            LayoutInflater
                .from(parent.context)
                .inflate(R.layout.list_item, parent, false)
        )
    

    override fun onBindViewHolder(holder: ViewHolder, position: Int) 
        val listItem = dataSet[position]

        //perform your binding

        holder.listItem = listItem
        holder.percentDownloaded?.text = listItem.percentDownloaded.toString()
        holder.setUpdateViewLambda()
    

    override fun getItemCount(): Int 
        return dataSet.size
    


data class ListItem(
    val id: Int, //a value that uniquely represents this list item
    var percentDownloaded: Byte //can only ever be between 0-100, so no need for Int
)

class MyDownloadProgressListener : OnProgressListener 
    lateinit var listItem: ListItem
    private var updateView: (progress: Progress) -> Unit = 
    fun setLabmda(lambda: (progress: Progress) -> Unit) 
        updateView = lambda
    

    override fun onProgress(progress: Progress?) 
        Log.d("Adapter", "Progress object: $progress")
        if (progress != null) 
            val toPercent = toPercent(progress)
            Log.d("Adapter", "Percent received: $toPercent")
            listItem.percentDownloaded = toPercent
            updateView(progress)
        
    


fun downloadItem(
    listItem: ListItem,
    adapterPosition: Int,
    adapter: RecyclerViewAdapter,
    url: String,
    dirPath: String,
    filename: String
) 
    val listener = MyDownloadProgressListener()
    mutableMapOfIDToProgressListenerCallback[listItem.id] = listener
    listener.listItem = listItem
    Handler(Looper.getMainLooper()).post  adapter.notifyItemChanged(adapterPosition)/*bind listener to ViewHolder*/ 

    PRDownloader
        .download(url, dirPath, filename)
        .build()
        .setOnProgressListener(listener)
        .setOnCancelListener 
                mutableMapOfIDToProgressListenerCallback.remove(listItem.id)
        
        .start(object : OnDownloadListener 
            override fun onDownloadComplete() 
                mutableMapOfIDToProgressListenerCallback.remove(listItem.id)
            

            override fun onError(error: Error?) 
                mutableMapOfIDToProgressListenerCallback.remove(listItem.id)
            
        
    )


fun toPercent(progress: Progress) =
    (100 * (progress.currentBytes / progress.totalBytes.toDouble())).toInt().toByte()

【讨论】:

以上是关于在 Android 的 Recyclerview 中显示下载进度的主要内容,如果未能解决你的问题,请参考以下文章

android之recyclerview的基本使用

android: ScrollView 内的 RecyclerView

RecyclerView:错误的滚动效果(Android)

Android:在 Recyclerview 中更改图像旋转

Android Studio(Kotlin)之RecyclerView

Android RecyclerView的基本使用