Scenario:
RecyclerView
with aRecyclerView.Adapter
. ThenotifyDataSetChanged()
-method is called every 10ms because time sensitive information is shown, so the ViewHolders (class is extendingRecyclerView.ViewHolder
) are updated frequently.- The underlying data comes from a native C-library, so the Adapter-methods are overridden to query the data from the native library.
- derived from this best practice video an interface was implemented in the Adapter-class to pass the onTouch-Events from the Adapter to the Activity. Therefore the ViewHolder-class implements
View.OnTouchListener
and passes all touch events and the listItem-position (queried bygetAdapterPosition()
) via the interface to the Activity. - various onTouch-events (Click, LongClick, Swipe etc.) for each ListItem should be recognized in the activity.
Problem:
When a ListItem is touched shortly afternotifyDataSetChanged()
has been called, the received values are:
1 getAdapterPosition()
: -1
2 motionEvent.getAction()
: ACTION_DOWN
directly followed by ACTION_CANCEL
The android-documentation says:
Note that if you've called notifyDataSetChanged(), until the next layout pass, the return value of this method will be NO_POSITION.
So I guess the error occurs everytime the listItem is touched while notifyDatasetChanges()
refreshes the viewHolders: getAdapterPosition()
returns -1 according to the documentation. And, probably because the elements in the viewHolder are refreshed, the onTouchEvent throws a ACTION_CANCEL
.
what I tried:
- I tried to stop the refreshing of the RecyclerView on
ACTION_DOWN
, but because of theACTION_CANCEL
event, I do not receive anyACTION_UP
event and can not restart the refreshing of the data. - I added a
RecyclerView.OnItemTouchListener()
to the recyclerView in the activity to receive the TouchEvent inonInterceptTouchEvent()
before it is passed to the listItem and its onTouchListener and stop the refreshing of the recyclerview there. But, because I still need information about what item was clicked, I still need the items onTouchListener which is still returning -1 andACTION_CANCEL
.
Question:
What is the best practice to handle onTouch events in a RecyclerView which is frequently updating its data?
Activity-class
public class Activity extends AppCompatActivity implements DataStoreDataAdapter.OnListItemTouchListener {
Handler dataUpdateHandler = new Handler();
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
dataStoreDataAdapter.notifyDataSetChanged();
dataUpdateHandler.postDelayed(this, 10);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
dataStoreDataAdapter = new DataStoreDataAdapter(getApplicationContext(),this);
recyclerView = findViewById(R.id.list_view);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(dataStoreDataAdapter);
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
Log.i(TAG, "onInterceptTouchEvent: " + " touched by type " + e);
int action = e.getAction();
if (action == MotionEvent.ACTION_DOWN) {
dataUpdateHandler.removeCallbacks(timerRunnable);
} else if (action == MotionEvent.ACTION_UP) {
dataUpdateHandler.post(timerRunnable);
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
Log.i(TAG, "onTouchEvent: " + " touched by type " + e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
//set update Handler
dataUpdateHandler.post(timerRunnable);
}
@Override
public void onListItemTouch(int position, MotionEvent motionEvent) {
Log.i(TAG, "onListItemTouch: ListItem position " + position + " motionEvent: " + motionEvent);
}
}
Adapter-class
public class DataStoreDataAdapter extends RecyclerView.Adapter<DataStoreDataAdapter.ViewHolder> {
private OnListItemTouchListener onListItemTouchListener;
static class ViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener{
View view;
TextView Name;
// ...
ViewHolder(@NonNull View itemView, OnListItemTouchListener onListItemTouchListener) {
super(itemView);
this.Name = itemView.findViewById(R.id.NameListItem);
// ...
this.onListItemTouchListener = onListItemTouchListener;
itemView.setOnTouchListener(this);
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
onListItemTouchListener.onListItemTouch(getAdapterPosition(), motionEvent);
return true;
}
}
public interface OnListItemTouchListener{
void onListItemTouch(int position, MotionEvent motionEvent);
}
public DataStoreDataAdapter(Context context, OnListItemTouchListener onListItemTouchListener) {
super();
this.onListItemTouchListener = onListItemTouchListener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.list_item,parent,false);
return new ViewHolder(view, onListItemTouchListener);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// get the stored data for this position
// ...get stuff from native library
// put data into view elements...
}
@Override
public int getItemCount() {
return // ... itemCount from native library;
}
}