SwipeLayout解析-事件分发

2018-07-21  本文已影响138人  BooQin

前言

在上一篇《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

上一篇 下一篇

猜你喜欢

热点阅读