场景:
RecyclerView
和RecyclerView.Adapter
。由于会显示对时间敏感的信息,因此notifyDataSetChanged()
方法每10毫秒被调用一次,因此ViewHolders(类正在扩展RecyclerView.ViewHolder
)会经常更新。View.OnTouchListener
并将所有触摸事件和listItem-position(由getAdapterPosition()
查询)通过接口传递给Activity。问题:
在调用notifyDataSetChanged()
之后不久触摸ListItem时,接收到的值为:
1 getAdapterPosition()
:-1
2 motionEvent.getAction()
:ACTION_DOWN
后接ACTION_CANCEL
请注意,如果您调用了notifyDataSetChanged(),则直到下一个 布局传递,此方法的返回值为NO_POSITION。
因此,我猜测notifyDatasetChanges()
刷新viewHolders时,每次触摸listItem都会发生错误:getAdapterPosition()
根据文档返回-1。而且,可能是因为刷新了viewHolder中的元素,所以onTouchEvent引发了ACTION_CANCEL
。
我尝试过的方法:
ACTION_DOWN
上停止RecyclerView的刷新,但是由于发生ACTION_CANCEL
事件,我没有收到任何ACTION_UP
事件,并且无法重新开始刷新数据。RecyclerView.OnItemTouchListener()
,以在onInterceptTouchEvent()
中接收TouchEvent,然后将其传递给listItem及其onTouchListener并在那里停止刷新recyclerview。
但是,因为我仍然需要有关单击了哪个项目的信息,所以我仍然需要onTouchListener项,该项仍返回-1和ACTION_CANCEL
。问题:
在经常更新其数据的RecyclerView中处理onTouch事件的最佳实践是什么?
活动级别
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);
}
}
适配器类
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;
}
}
答案 0 :(得分:0)
我认为发生的事情是notifyDataSetChanged仍在进行中,而在此期间发生触摸时,这些项目没有位置,并且我认为这无法解决。
在我看来,侦听器很好,您真正应该更改的是notifyDataSetChanged()部分。
您可以改用DiffUtil,以便仅更新实际更改的项目(位置或内容)。
转到此处以获取更多有关如何实现此目的的信息: https://medium.com/@iammert/using-diffutil-in-android-recyclerview-bdca8e4fbb00
答案 1 :(得分:0)
我自己弄清楚了。 答案如下:
getAdapterPosition(): -1
:
正如我在问题中已经提到的那样,这是因为notifyDataSetChanged()
重新构建了整个布局。因此,如果在布局重新生成和布局传递之间触摸了列表项,则该函数将返回-1。 Finn Marquart提供了一种解决方案:使用DiffUtils将列表中每个项目的刷新量减少到最少。这对我不起作用,因为我需要经常刷新该项目。因此,我的解决方案是使用notifyItemRangeChanged(0,devicedataDataAdapter.getItemCount())
而不是notifyDataSetChanged()
,因为这只会刷新视图持有者中的元素,而不会重新创建整个列表。onTouch()
方法都会收到一个ACTION_CANCEL
事件,并且不再会收到有关该手势的通知(ACTION_UP
事件可以仅在父视图(如OnItemTouchListener()
的{{1}}上收到)。 负责任的是itemAnimator ,它是recyclerview的成员。通过将itemAnimator设置为null来禁用itemAnimator之后,触摸手势将不再被拦截: recyclerview
希望,这个答案可以帮助任何人。我花了很多时间解决这个问题:)
*我将“手势”定义为ACTION_DOWN和ACTION_UP事件之间所有触摸事件的总和。