RecyclerView默认没有像ListView一样提供setOnItemClickListener()
接口,网上大部分的解决方案都是通过给每个item添加onClickListener来模仿一个伪onItemClickListener,这种为每个item添加点击监听的解决方案浪费性能。
查阅RecyclerView的api发现虽然没有提供onItemClickListener但是提供了addOnItemTouchListener
方法:
RecyclerView.addOnItemTouchListener(OnItemTouchListener listener)
既然可以添加触摸监听,那么我们完全可以获取触摸手势来识别点击事件,然后通过触摸坐标来判断点击的是哪一个item。
使用方法:
recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
//item点击事件
}
});
其中OnRecyclerItemClickListener
是自定义的一个触摸监听器:
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener{
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView){
this.recyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(),new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child!=null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemClick(vh);
}
return true;
}
//长点击事件,本例不需要不处理
//@Override
//public void onLongPress(MotionEvent e) {
// View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
// if (child!=null) {
// RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
// onItemLongClick(vh);
// }
//}
public abstract void onItemClick(RecyclerView.ViewHolder vh);
//public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
}
RecyclerView提供了设置触摸监听的方法,那么我们定义一个类OnRecyclerItemClickListener实现OnItemTouchListener
,我们需要实现其3个方法:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
其中第三个方法是处理触摸事件冲突的,前两个方法是View的事件分发机制里面的事件拦截和事件处理的两个方法。参数里为我们提供了触摸事件的数据MotionEvent
,我们要做的就是去解析坐标点和触摸规律来识别触摸手势,然后获取触摸的是哪一个item,再执行我们的回调
GestureDetectorCompat
就是处理手势的类:手势探测器,它比GestureDetector
能更好兼容低版本的api,但使用方法是一致的,我们实例化一个手势探测器:
mGestureDetector = new GestureDetectorCompat(context,new GestureListener(){...});
我们实例化手势探测器的时候需要提供一个手势监听器:OnGestureListener
,探测器识别出手势后就会回调手势监听器中对应的方法,我们就可以在回调方法中做我们想做的事情了。
sdk提供了两个手势监听器:OnGestureListener
,OnDoubleTapListener
OnGestureListener的回调接口如下:
//用户按下屏幕就会触发
public boolean onDown(MotionEvent e);
//如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行
public void onShowPress(MotionEvent e);
//一次单独的轻击抬起操作,也就是轻击一下屏幕,就是普通点击事件
public boolean onSingleTapUp(MotionEvent e);
//在屏幕上拖动事件
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//长按触摸屏,超过一定时长,就会触发这个事件
public void onLongPress(MotionEvent e);
//滑屏,用户按下触摸屏、快速移动后松开
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
OnDoubleTapListener的回调接口如下
//单击事件。用来判定该次点击是SingleTap而不是DoubleTap,
//如果连续点击两次就是DoubleTap手势,如果只点击一次,
//系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,
//然后触发SingleTapConfirmed事件
public boolean onSingleTapConfirmed(MotionEvent e);
//双击事件
public boolean onDoubleTap(MotionEvent e);
//双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作
public boolean onDoubleTapEvent(MotionEvent e);
可以看出OnGestureListener主要回调各种单击事件,而OnDoubleTapListener回调各种双击事件。而我们需要处理的点击事件其实就是上面的:onSingleTapUp()
定义一个ItemTouchHelperGestureListener 继承自SimpleOnGestureListener ,实现onSingleTapUp方法:
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
}
}
到这里,已经获取到了RecyclerView的点击事件和触摸事件数据MotionEvent ,那么我们怎么知道点击的是哪一个item呢?RecyclerView已经为我们提供了这样的方法:findChildViewUnder()
,我们可以通过这个方法获得点击的item,同时我们调用RecyclerView的另一个方法getChildViewHolder()
,可以获得该item的ViewHolder,最后再回调我们定义的虚方法onItemClick()
就ok了,这样我们就可以在外部实现该方法来获得item的点击事件了:
@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child!=null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemClick(vh);
}
return true;
}
前述的方案通过recyclerView.addOnItemTouchListener(...)
添加点击事件的方法,其实我们也完全可以把点击事件写在Adapter的onBindViewHolder()中,不暴露出来,即:
public void onBindViewHolder(VH holder, int position) {
holder.itemView.setOnClickListener(...);
}