Android RecyclerView 基于SimpleOnItemTouchListener的改进

熟悉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时注册监听的控件,达到动态修改和多个控件排除的功能。

该示例只是抛砖引玉,还有许多东西亟待完善,但大体思路就是这样。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值