问题
I was recently playing a little bit with the RecyclerView and the concept of ViewHolder's in Android 5+. The adapter implementation handles quite a large dataset (i.e ~ 60 000 items), requested from the internet upon onBindViewHolder(...) call. The network requests are handled by the volley library and the response represents the data model used to fill up the view holder.
Now when fast-flinging (i.e motion with a velocity, different than swipe where a user touches the device and moves slowly across the screen) the onBindViewHolder is called for all positions in the adapter, which is not a good thing as most of the viewholders can't be visible due to high speed of the animation. For example, if user swipes from position 5 to 300 in a matter of seconds most of the views can't be readable, yet the bind method is called. Since the adapter gets the data model from the network - a lot of requests are made, most of them are of no use, and the hole process just delays the processing of requests around position 300 which is the one that user can realy observe.
How would one approach this issue of requesting only the visible positions? My first approach was to prioritize the requests in the volley processing queue. This really didn't solve much of the problem since every network request is processed just in a different order. My second approach was to cancel all existing requests when a new one is added. It didn't work for me really well and from a design point of view I don't like this solution.
My real question is then, how would you approach this issue?
回答1:
Yes, I've managed to fix my issues by keeping the state of each adapter position. The RecyclerView.Adapter has two methods onViewAttachedToWindow(VH holder)
and onViewDetachedFromWindow(VH holder)
signaled when the view is about to be seen by the user or when it's removed so that the user can't see it.
I created a simple singleton class to keep the state of each adapter position:
import java.util.HashSet;
import java.util.Set;
/**
* Created by fauri on 14/06/2015.
*/
public class ViewHolderState {
private static final ViewHolderState ourInstance = new ViewHolderState();
private Set<Integer> recycled = new HashSet<>();
private ViewHolderState() {
}
public static ViewHolderState getInstance() {
return ourInstance;
}
public void addRecycledView(int position) {
recycled.add(position);
}
public void removeRecycledView(int position) {
recycled.remove(position);
}
public boolean isViewRecycled(int position) {
return recycled.contains(position);
}
public void clearAll() {
recycled.clear();
}
}
While in the adapter class:
private ViewHolderState viewHolderState = ViewHolderState.getInstance();
@Override
public void onViewDetachedFromWindow(CardViewHolder holder) {
super.onViewDetachedFromWindow(holder);
viewHolderState.addRecycledView(holder.getAdapterPosition());
}
@Override
public void onViewAttachedToWindow(CardViewHolder holder) {
super.onViewAttachedToWindow(holder);
viewHolderState.removeRecycledView(holder.getAdapterPosition());
}
This is just to keep the state of each adapter position. Now in the business logic that triggers the network calls, you will just have to get the singleton instance and check if isViewRecycled(int adapterPosition)
. If the view is recycled then no need to make the network call.
Of course this is a simple solution. The code can be improved by adding thread safety (i.e either use Collection.synchronizedSet(...) or make the methods synchronzied) or just use another pattern. Java guru's don't really dig the singleton pattern.
Anyway the general idea is to make use of onViewAttachedToWindow
and onViewDetachFromWindow
来源:https://stackoverflow.com/questions/29615524/android-fling-actions-on-a-recyclerview