Created
January 17, 2017 11:34
-
-
Save davidjoneshedgehog/246cf7a351944e525fb96ba0559c8bea to your computer and use it in GitHub Desktop.
Android activity demonstrating having progress bars within RecyclerView elements and how these progress bars can be updated from an Asynchronous task.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.hedgehoglab.progressrecyclerviewsample; | |
import android.os.AsyncTask; | |
import android.os.Bundle; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.support.v7.app.AppCompatActivity; | |
import android.support.v7.widget.LinearLayoutManager; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.Log; | |
import android.util.SparseArray; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.View.OnClickListener; | |
import android.view.ViewGroup; | |
import android.widget.ProgressBar; | |
import android.widget.TextView; | |
import java.lang.ref.WeakReference; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* This class handles hosting {@link ProgressBar} views within {@link RecyclerView} items, by default | |
* if a {@link android.support.v7.widget.RecyclerView.ViewHolder} is designated as a listener and passed | |
* to another thread then as this ViewHolder is recycled the listener is also recycled. This means | |
* that when the thread calls back to the ViewHolder it is acting on the recycled cell (e.g. start | |
* task on progress bar one, scroll down, and progress bar 20 might be updating when on screen). | |
* | |
* This class demonstrates attaching and detaching the ViewHolder listener as the ViewHolder is being | |
* bound and recycled respectively, ensuring that the task is only updating the desired ViewHolder. | |
*/ | |
public class MainActivity extends AppCompatActivity { | |
private static final String TAG = MainActivity.class.getSimpleName(); | |
private static final int NUM_OF_ITEMS = 50; | |
private SparseArray<WaitingTask> mWaitingTaskSparseArray = new SparseArray<>(); | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_recyclerview); | |
LinearLayoutManager llm = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false); | |
recyclerView.setLayoutManager(llm); | |
ProgressAdapter progressAdapter = new ProgressAdapter(); | |
recyclerView.setAdapter(progressAdapter); | |
List<ProgressObject> progressObjects = new ArrayList<>(); | |
for (int i = 0; i < NUM_OF_ITEMS; i++) { | |
progressObjects.add(new ProgressObject(i, "Position " + i, 0)); | |
} | |
progressAdapter.updateProgressObjects(progressObjects); | |
} | |
private interface WaitingListener { | |
void onProgressUpdated(int progress); | |
} | |
private class ProgressAdapter extends RecyclerView.Adapter<ProgressViewHolder> { | |
private List<ProgressObject> mProgressObjectList = new ArrayList<>(); | |
void updateProgressObjects(@NonNull List<ProgressObject> progressObjects) { | |
mProgressObjectList = progressObjects; | |
notifyDataSetChanged(); | |
} | |
@Override | |
public ProgressViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | |
View root = LayoutInflater.from(parent.getContext()).inflate(R.layout.viewholder_progress_object, parent, false); | |
return new ProgressViewHolder(root); | |
} | |
@Override | |
public void onBindViewHolder(ProgressViewHolder holder, int position) { | |
holder.bind(mProgressObjectList.get(position)); | |
} | |
/** | |
* If a task has been created for the {@link ProgressObject} for this {@link ProgressViewHolder} | |
* then the listener is removed and the view is then recycled | |
* @param holder {@link ProgressViewHolder} ViewHolder to recycle | |
*/ | |
@Override | |
public void onViewRecycled(ProgressViewHolder holder) { | |
WaitingTask task = mWaitingTaskSparseArray.get(holder.getId()); | |
if (task != null) { | |
task.updateListener(null); | |
} | |
super.onViewRecycled(holder); | |
} | |
@Override | |
public int getItemCount() { | |
return mProgressObjectList.size(); | |
} | |
} | |
private class ProgressViewHolder extends RecyclerView.ViewHolder implements WaitingListener { | |
private ProgressBar mProgressBar; | |
private TextView mProgressText; | |
private int mId; | |
ProgressViewHolder(View itemView) { | |
super(itemView); | |
mProgressBar = (ProgressBar) itemView.findViewById(R.id.viewholder_progress_bar); | |
mProgressText = (TextView) itemView.findViewById(R.id.viewholder_progress_text); | |
} | |
/** | |
* When binding the {@link ProgressObject} to the ViewHolder, ensure that there is some form | |
* of ID held as a member variable to identify the task for this item, then update the | |
* progress bar to it's current value and if a task already exists for this data item, update | |
* the listener. | |
* | |
* @param progressObject {@link ProgressObject} Data to be bound | |
*/ | |
void bind(final ProgressObject progressObject) { | |
mId = progressObject.getId(); | |
mProgressText.setText(progressObject.getTitle()); | |
mProgressBar.setProgress(progressObject.getProgress()); | |
WaitingTask task = mWaitingTaskSparseArray.get(mId); | |
if (task != null) { | |
task.updateListener(ProgressViewHolder.this); | |
} | |
itemView.setOnClickListener(new OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
// Create the task, set the listener, add to the task controller, and run | |
WaitingTask task = new WaitingTask(progressObject, ProgressViewHolder.this); | |
mWaitingTaskSparseArray.put(mId, task); | |
task.execute(); | |
} | |
}); | |
} | |
@Override | |
public void onProgressUpdated(int progress) { | |
// Must be run on the UI thread, updates the progress bar to a new value via the callback | |
mProgressBar.setProgress(progress); | |
} | |
public int getId() { | |
return mId; | |
} | |
} | |
private class WaitingTask extends AsyncTask<Void, Integer, Void> { | |
private ProgressObject mProgressObject; | |
private WeakReference<WaitingListener> mWaitingListenerWeakReference; | |
WaitingTask(ProgressObject progressObject, WaitingListener waitingListener) { | |
mProgressObject = progressObject; | |
updateListener(waitingListener); | |
} | |
void updateListener(@Nullable WaitingListener waitingListener) { | |
mWaitingListenerWeakReference = new WeakReference<>(waitingListener); | |
} | |
@Override | |
protected Void doInBackground(Void... voids) { | |
try { | |
for (int i = 0; i <= 100; i++) { | |
Thread.sleep(50); // Random delay | |
mProgressObject.setProgress(i); // Update data set | |
publishProgress(i); // Inform UI of progress | |
} | |
} catch (InterruptedException ie) { | |
Log.e(TAG, "Interrupted Exception: " + ie.getLocalizedMessage()); | |
} | |
return null; | |
} | |
@Override | |
protected void onProgressUpdate(Integer... values) { | |
super.onProgressUpdate(values); | |
if (mWaitingListenerWeakReference != null) { | |
WaitingListener listener = mWaitingListenerWeakReference.get(); | |
if (listener != null) { | |
listener.onProgressUpdated(values[0]); | |
} | |
} | |
} | |
} | |
private class ProgressObject { | |
private int mId; | |
private String mTitle; | |
private int mProgress; | |
ProgressObject(int id, String title, int progress) { | |
mId = id; | |
mTitle = title; | |
mProgress = progress; | |
} | |
int getId() { | |
return mId; | |
} | |
void setId(int id) { | |
mId = id; | |
} | |
String getTitle() { | |
return mTitle; | |
} | |
void setTitle(String title) { | |
mTitle = title; | |
} | |
int getProgress() { | |
return mProgress; | |
} | |
void setProgress(int progress) { | |
mProgress = progress; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you please post this on stackexchange? I wish I found this a long time a ago!