在RecyclerView中更新ProgressBar

16

我有一个RecyclerView。其中,项具有标准布局-一个TextView和一个ProgressBar。 运行时添加项目到RecyclerView。 每当添加一个项目时,都会启动一个AsyncTask来更新ProgressBar。 AsynTask保存对来自RecyclerView Adapter的ProgressBar对象的引用。 当RecyclerView中的项目太多时,问题就会出现。

我知道RecyclerView会回收任何旧视图,因此至少想要解决ProgressBar的方式。

实现这一点的理想方式是什么?

以下是适配器的摘录

public class RecViewAdapter extends
        RecyclerView.Adapter<RecViewAdapter.ViewHolder> {
    Context mContext;
    List<String> mRunns;
    static ExecutorService mExec;
    static HashSet<Integer> mProcessed = new HashSet<>();

    public RecViewAdapter(Context context, List<String> runns) {
        mContext = context;
        mRunns = runns;
        mExec = Executors.newFixedThreadPool(1);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.runnabel_item, viewGroup,
                false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.runnName.setText(mRunns.get(position));
        if (!mProcessed.contains(position)) {
            new ProgressTask(holder.pBar, position).executeOnExecutor(mExec, null);
            mProcessed.add(position);
        }
    }

    @Override
    public int getItemCount() {
        return mRunns.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView runnName;
        ProgressBar pBar;

        public ViewHolder(View view) {
            super(view);
            runnName = (TextView) view.findViewById(R.id.textView);
            pBar = (ProgressBar) view.findViewById(R.id.progressBar);
            pBar.setIndeterminate(false);
            pBar.setMax(100);
            pBar.setProgress(0);

        }
    }
}

此外,我正在使用notifydatasetchanged向RecyclerView添加条目。


我正在尝试通过广播接收器使各个异步任务更新其各自的视图。在每次发布更新时,异步任务将发送广播意图。广播接收器将调用活动中的函数,实际上更新进度条。 - Prathamesh Shetye
3个回答

7
您可以简单地使用Recycler View的“findViewHolderForAdapterPosition”方法,然后从中获取一个viewHolder对象,然后将该viewHolder转换为您的adapter viewHolder类型,这样就可以直接访问您的viewHolder视图(在本例中,我们访问progressBar)。
以下是Kotlin示例代码:
 /**
     * update progress bar in recycler view
     * get viewHolder from position and progress bar from that viewHolder
     *  we are rapidly updating progressbar so we did't use notify method as it always update whole row instead of only progress bar
     *  @param position : position of list cell
     *  @param progress : new progress value
     */
    private fun updateDownloadProgressBar(position :Int, progress:Int)
    {
        val viewHolder = recyclerViewDownload.findViewHolderForAdapterPosition(position)
        (viewHolder as ViewHolderDownload).progressBarDownload.progress=progress
    }

运行得很顺利...谢谢 - AKTanara

4
有点晚了,但我找到了解决方法。
我的RecyclerView包含大量的viewholder,只有一个viewholder有进度条。我有一个SQLite数据库,其中维护了标识符,用于在我的服务和活动之间进行同步(以识别需要更新的recyclerview中的视图)。
根据您的实现方式,您需要找到一种方法来确定哪个广播事件对应于哪个适配器项。我在下面提供了我所做的简化版本。
进度条模型:
class ProgressModel{
    String progressId;
    int progress = 0;
}

public int getProgress(){
    return progress;
}

ViewHolder:

public class ProgressViewHolder extends RecyclerView.ViewHolder {
    private ProgressBar mProgressBar;

    public ProgressViewHolder(View itemView) {
        mProgressBar = (ProgressBar) itemView.findViewById(R.id.mProgressBar);
    }

    public ProgressBar getProgressBar() {
        return mProgressBar;
    }   
}

在RecyclerView适配器中,
@Override
public void onBindViewHolder(ProgressViewHolder holder, int position) {
    ProgressModel item = mData.get(position);
    int progress = item.getProgress();
        if (progress > 0) {
            ProgressBar downloadProgress = holder.getProgressBar();
            if (downloadProgress.isIndeterminate()) {
                downloadProgress.setIndeterminate(false);
            }
            downloadProgress.setProgress(progress);
        }
}

public void refresh(position, ProgressModel item){
    mData.set(position, item);
    notifyItemChanged(position);
}

在实现填充视图的Activity中,创建一个静态实例并将其传递给BroadcastReceiver。我花了很长时间才弄清楚需要静态实例,否则即使调用notifyItemchanged(),视图也不会改变。
public class MainActivity extends Activity{
    private static MainActivity instance;
    private MyReceiver mReceiver;
    private MyAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        instance = this;
        mReceiver = new MyReceiver(instance);
        //TODO: Initialize adapter with data and set to recyclerview
    }

    public void update(Intent intent){
        ProgressModel model = new ProgressModel ();
        //TODO: set progress detail from intent to model and get position from progressId
        instance.mAdapter.refresh(position,model);
    }

    private static class MyReceiver extends BroadcastReceiver {
        MainActivity activity;

        public DownloadReceiver(MainActivity activity) {
            this.activity = activity;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            //pass intent with progress details to activity
            activity.update(intent);
        }
    }

}

希望这能帮到你。

1
你永远不应该持有一个 Activity 的静态引用。Activity 可能会被销毁和重新创建,因此持有静态引用是导致内存泄漏的简单方法。 - cincy_anddeveloper
此外,您的静态引用是不必要的,可以删除。在update(...)中直接访问适配器。那个静态引用可能会导致错误。您可以使您的BroadcastReceiver非静态,并直接调用update(...)。请记住在适当的生命周期方法中注册/注销。不要使用静态变量引用UI组件。 - cincy_anddeveloper

1

对于那些刚开始学习Android开发且只了解Java的人来说,@Mayank Sharma提供的上述代码可能会让他们感到困惑。但这段代码确实非常有效。我将提供他答案的Java版本:

void updateProgress(int position, int progress)
{
    MyAdapter.MyHolder mHolder =
            (MyAdapter.MyHolder) myRecyclerView.findViewHolderForAdapterPosition(position);
    mHolder.customProgress.setProgress(progress);
}

在您包含进度条的Recycler View的活动中创建此方法,并在需要更改进度条时调用此方法。请保留HTML标签。

它显示了 holder 的取消引用可能会产生 NullPointerException,而我确实遇到了这个 NullPointerException。我是不是漏掉了什么? - StackOverflower

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接