美文网首页
SwipeLayout解析-事件分发

SwipeLayout解析-事件分发

作者: BooQin | 来源:发表于2018-07-21 20:54 被阅读138次

    前言

    在上一篇《SwipeLayout解析-动画篇》中,我们了解到其动画的实现主要依赖于ViewDragHelper来完成。而本文将结合《View事件分发浅析》,来分析在SwipeLayout中是如何处理事件的冲突的。

    SwipeLayout事件冲突处理

    在SwipeLayout中,对其内部的子View做滑动的处理,即重写onTouchEvent方法,onTouchEvent方法的部分代码如下:

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            //1.如果屏蔽了抽屉效果,直接运行父类的onTouchEvent并返回
            if (!isSwipeEnabled()) {
                return super.onTouchEvent(event);
            }
    
            //2.处理点击事件
            int action = event.getActionMasked();
            gestureDetector.onTouchEvent(event);
    
            //3.交由DragHelper,完成子View的动画更新
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mDragHelper.processTouchEvent(event);
                    sX = event.getRawX();
                    sY = event.getRawY();
    
                case MotionEvent.ACTION_MOVE: {
                    //the drag state and the direction are already judged at onInterceptTouchEvent
                    checkCanDrag(event);
                    if (mIsBeingDragged) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                        mDragHelper.processTouchEvent(event);
                    }
                    break;
                }
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mIsBeingDragged = false;
                    mDragHelper.processTouchEvent(event);
                    break;
    
                default://handle other action, such as ACTION_POINTER_DOWN/UP
                    mDragHelper.processTouchEvent(event);
            }
    
            return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN;
        }
    

    在该方法中主要分为三个部分,首先,会去判断是否开启了抽屉效果,如果未开启就会去调用父类View下的onTouchEvent,否则就执行SwipeLayout下的处理方法,然后,调用gestureDetector中的方法,这里主要做了对点击事件的处理,比如click,longClick等监听事件的响应,最后会做一个Switch判断,其调用了DragHelper的processTouchEvent,即将手势事件传递到ViewDragHelper中处理。
      在onTouchEvent中,如果要消耗点击事件,就需要在返回值中设置为true,而在SwipeLayout中重写的onTouchEvent,我们可以看到由三个值决定,并且是或关系,对于ACTION_DOWN事件,会返回true值进行事件的处理和消耗,因此,在开启了SwipeEnabled的情况下,当事件传递到SwipeLayout上时,所有的手势都会在onTouchEvent被消耗。
      在SwipeLayout重载了onTouchEvent后,我们可以在Activity中使用了,如果我们仅仅是单独使用SwipeLayout,会发现一起都很顺利,左滑,下滑等等操作都能实现对应的效果。但是,当我们需要嵌套SwipeLayout的时候,或者在其子View中添加点击事件时,就会发现对于SwipeLayout的手势动画全部无效了,这就是事件冲突了。我们需要使用onInterceptTouchEvent结合oequestDisallowInterceptTouchEvent方法组合来解决该类问题。
      通过View事件分发浅析,我们可以知道,如果ViewGroup的onInterceptTouchEvent方法返回了True,那么该事件就会被ViewGroup拦截,交由其onTouchEvent处理,并且不会再传递到子View中。因此,我们可以再SwipeLayout中设置该方法来达到分事件拦截的目的。该控件的作者在该方法做了如下实现:

        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if(mForceInterceptAble){
                return mForceIntercept;
            }
            if (!isSwipeEnabled()) {
                return false;
            }
            if (mClickToClose && getOpenStatus() == Status.Open && isTouchOnSurface(ev)) {
                return true;
            }
            for (SwipeDenier denier : mSwipeDeniers) {
                if (denier != null && denier.shouldDenySwipe(ev)) {
                    return false;
                }
            }
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDragHelper.processTouchEvent(ev);
                    mIsBeingDragged = false;
                    sX = ev.getRawX();
                    sY = ev.getRawY();
                    //if the swipe is in middle state(scrolling), should intercept the touch
                    if (getOpenStatus() == Status.Middle) {
                        mIsBeingDragged = true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    boolean beforeCheck = mIsBeingDragged;
                    checkCanDrag(ev);
                    if (mIsBeingDragged) {
                        ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(true);
                        }
                    }
                    if (!beforeCheck && mIsBeingDragged) {
                        //let children has one chance to catch the touch, and request the swipe not intercept
                        //useful when swipeLayout wrap a swipeLayout or other gestural layout
                        return false;
                    }
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mIsBeingDragged = false;
                    mDragHelper.processTouchEvent(ev);
                    break;
                default://handle other action, such as ACTION_POINTER_DOWN/UP
                    mDragHelper.processTouchEvent(ev);
            }
            return mIsBeingDragged;
        }
    

    首先,根据代码可以知道,在不同的手势事件下有不同返回值。以SwipeLaytout A嵌套SwipeLayout B为例。
      Down事件:该事件我们必须返回false,不然所有的事件都无法传递到子View。而在SwipeLayout中只有在动画执行中返回true。
      MOVE事件:事件处理的重点在于MOVE事件,可以通过该手势来判断是点击还是拖拽操作,在SwipeLayout中,通过beforeCheck和mIsBeingDragged两个标志为来决定是否拦截,从代码可知,前者是mIsBeingDragged的初始值,当用户滑动的时候,mIsBeingDragged会在checkCanDrag(ev)被置为True,然后就会调用requestDisallowInterceptTouchEvent方法,该方法可以使父类的onInterceptTouchEvent值无效,紧接着做了一个判断:

        if (!beforeCheck && mIsBeingDragged) {
            //let children has one chance to catch the touch, and request the swipe not intercept
            //useful when swipeLayout wrap a swipeLayout or other gestural layout
            return false;
        }
    

    该方法是解决嵌套冲突的关键,其核心思想是在第一次判断为滑动的情况下不进行拦截,继续传递,所以SwipeLayout A在第一次Move事件中不进行拦截,将事件传递到SwipeLayout B(下一层View)中,这样便为SwipeLayout B提供了一次执行requestDisallowInterceptTouchEvent方法的机会,也以此来解决了嵌套的冲突情况。
      UP和CANCEL事件:该事件并未做特殊处理,只是将事件交由ViewDragHelper处理。
      通过以上的设置,就可以解决嵌套的事件冲突了,效果如下图:

    SwipeLayout_GIF

    但是在实际的使用过程中,发现可能出现滑动混乱的现象,在代码中的Down事件下mIsBeingDragged被置为True,这就导致了所有事件被拦截,具体原因初步判断时SwipeLayout的OpenStatus没有正确设置。
      关于SwipeLayout控件,我Fork到了自己的github上(ZhangFengG/AndroidSwipeLayout),并做了一些设置项的修改,添加了对onInterceptTouchEvent的手动设置,这样可以更直观的观察该方法在ViewGroup中的作用:

    SwipeLayout_Main

    相关资料

    end

    相关文章

      网友评论

          本文标题:SwipeLayout解析-事件分发

          本文链接:https://www.haomeiwen.com/subject/qalkmftx.html