熟悉RecyclerView的朋友应该知道,RecyclerView本身没有实现OnItemClick事件监听的功能,但是它给了我们一个基本事件监听OnItemTouchListener,官方给了一个基本实现SimpleOnItemTouchListener,但其实什么也没有处理:
public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener
{
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
在这里我们先要理解这三个函数的作用:
onInterceptTouchEvent 当返回true时,MotionEvent将被拦截在这一层,不会将MotionEvent传递给下一层view。
onTouchEvent 收到了onInterceptTouchEvent的处理后,无论返回什么,都会调用onTouchEvent进行处理。
onRequestDisallowInterceptTouchEvent 当有子View反对拦截MotionEvent的时候,会调用该方法。这里用不上,因为我们不做拦截。
所以当一个触摸事件来临是,首先MotionEvent的action值是ACTION_DOWN,紧接着几次事件是ACTION_MOVE,最后以ACTION_UP结束。我们可以用GestureDetectorCompat来识别这个event是短按、长按还是双击等等。
网上比较流行的实现RecyclerView的点击事件监听,是在ACTION_DOWN到来时进行拦截,然后使用GestureDetectorCompat分析这个事件,最后做处理(当然那种在adaptor中添加监听的方式不在这里讨论,我个人不太建议用那种方式实现)。进行拦截的后果是,触摸事件不能再对RecyclerView的子View产生效果。假如你的RecyclerView中有Button之类的,那么他们不会触发点击,显然有相当大的局限性。
另外一种不做拦截,但是他们没有做对其他控件的事件响应的过滤,这样会导致多余的点击事件响应。
针对上面的情况,我对他们的方法做了一些改进,在不做拦截的情况下实现点击事件监听。以下是主要的代码:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if (mGestureDetector == null) {
initGestureDetector(rv);
}
mGestureDetector.onTouchEvent(e);
return false;
}
private boolean isTouchPointInView(View view, int x, int y) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0];
int top = location[1];
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
if (view.isClickable() && y >= top && y <= bottom && x >= left
&& x <= right) {
return true;
}
return false;
}
private boolean isCatchID(ViewGroup viewGroup, int loopID, MotionEvent e){
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View viewchild = viewGroup.getChildAt(i);
long catchID = viewchild.getId();
long targetID = R.id.extra;
if(catchID == targetID){
ArrayList<View> touches = viewchild.getTouchables();
for(View tmpV : touches) {
if (isTouchPointInView(tmpV, (int) e.getRawX(), (int) e.getRawY())) {
return true;
}
}
}
else{
if(viewchild instanceof ViewGroup){
if(isCatchID((ViewGroup) viewchild, ++loopID, e)){
return true;
}
}
}
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
super.onTouchEvent(rv, e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.onRequestDisallowInterceptTouchEvent(disallowIntercept);
Log.d(Tags.debug, "Disallow event ID "+(clickID)+" "+disallowIntercept);
}
/**
* 初始化GestureDetector
*/
private void initGestureDetector(final RecyclerView recyclerView) {
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener() { // 这里选择SimpleOnGestureListener实现类,可以根据需要选择重写的方法
/**
* 单击事件
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return false;
}
}
if (childView != null && mListener != null) {
mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
/**
* 长按事件
*/
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return;
}
}
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
}
}
/**
* 双击事件
*/
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
int action = e.getAction();
if (action == MotionEvent.ACTION_UP) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return false;
}
}
if (childView != null && mListener != null) {
mListener.onItemDoubleClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
}
return false;
}
});
}
在这里我以R.id.extra为例,当识别动作完成后,对子View进行遍历查找R.id.extra控件,并判断触摸坐标是否发生在该控件上。如果是,则不调用回调函数,防止多余的触发回调动作。在实际应用当中,我们可以在添加listener时注册监听的控件,达到动态修改和多个控件排除的功能。
该示例只是抛砖引玉,还有许多东西亟待完善,但大体思路就是这样。