Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating a ProgressBar in a RecyclerView

I have a RecyclerView. In it, the items have a standard layout - one TextView and one ProgressBar. Items are added to the recyclerview at runtime. Whenever an Item is added, an AsyncTask is started which updates the ProgressBar. The AsynTask holds a reference to the ProgressBar object from the RecyclerView Adapter. The problem occurs when there are too many items in the recycler view.

I know the RecyclerView recycles any old views and thus want a way around that atleast for the progressbars.

What would be the ideal way to implement this?

Following is an excerpt from the Adapter

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);

        }
    }
}

Also, I'm adding items to the RecyclerView using notifydatasetchanged.

like image 993
Prathamesh Shetye Avatar asked Apr 26 '15 16:04

Prathamesh Shetye


People also ask

How do you solve the performance problem in a RecyclerView?

Use the setHasFixedsize method If the height of our RecyclerView items is fixed then we should use the setHasFixedsize method in our XML of our card item. This will fix the height of our RecyclerView item and prevent it from increasing or decreasing the size of our Card Layout.

What does notifyDataSetChanged do in RecyclerView?

notifyDataSetChanged. Notify any registered observers that the data set has changed. There are two different classes of data change events, item changes and structural changes.

What is expandable RecyclerView?

Expandable RecyclerView is one of the most important feature in Android which can be easily created for our application . It contains two views one is parent view and other is child view. Parent is visible by default but the child view has to be expanded and collapsed. It will expand when we click on parent view.


2 Answers

You can simply use "findViewHolderForAdapterPosition" method of recycler view and you will get a viewHolder object from that then typecast that viewHolder into your adapter viewholder, so you can directly access your viewholder's views, ( in this case we access progressBar)

following is the sample code for 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
    }
like image 126
Mayank Sharma Avatar answered Sep 20 '22 20:09

Mayank Sharma


A little late, but I found a way to get it working.

My recyclerview contains a large number viewholders and only one of the viewholders have a progress bar. I have an sqlite database in which I maintain identifiers which I use to sync between my service and activity (to identify which views in the recyclerview need updating).

Depending on your implementation, you will have to find a way to identify which broadcast event corresponds to which adapter item. I have given a simplified version of what I have done below.

Model for Progress Bar:

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;
    }   
}

In the recyclerview adapter,

@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);
}

In the Activity which implements populates the view, create a static instance of itself and pass it to the BroadcastReceiver. It took me quite a while to figure out that the static instance is required, otherwise the view doesn't get changed even though I call 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);
        }
    }

}

Hope this helps.

like image 37
mUser1990 Avatar answered Sep 18 '22 20:09

mUser1990