Android View事件分发

开发当中经常需要处理滑动冲突,而滑动冲突这种老大难的问题的理论基础就是事件分发机制

首先我们来认识下事件分发中3个主要的方法

  • public boolean dispatchTouchEvent(MotionEvent event)
    通过方法名我们不难猜测,它就是事件分发的重要方法。那么很明显,如果一个MotionEvent传递给了View,那么dispatchTouchEvent方法一定会被调用!
    返回值:表示当前事件是否被消费了。可能是View本身的onTouchEvent方法消费,也可能是子View的dispatchTouchEvent方法中消费。返回true表示事件被消费,本次的事件终止。返回false表示View以及子View均没有消费事件,将调用父View的onTouchEvent方法
  • public boolean onInterceptTouchEvent(MotionEvent ev)
    事件拦截,当一个ViewGroup在接到MotionEvent事件序列的时候,首先会调用此方法判断是否需要拦截。特别注意,这是ViewGroup特有的方法,View并没有拦截方法
    返回值:是否拦截事件传递,返回true表示拦截事件,那么事件将不再往下分发而调用View本身的onTouchEvent方法。返回false表示不做拦截,事件将乡下分发到子View的dispathtouchEvent方法
  • pulic boolean onTouchEvent(MotionEvent ev)
    真正对MotionEvent进行处理或者消费的方法。在dispatchTouchEvent进行调用。
    返回值:返回true表示事件被消费,本次的事件终止。返回false表示事件没有被消费,将调用父View的onTouchEvent方法
    通过下面的流程图,会更清晰的帮我们梳理事件分发机制
    image

对于View(ViewGroup也是View)而言,如果设置了onTouchListener,那么onTouchListener方法中的onTouch方法会被回调。onTouch返回true,则onTouchEnent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent-onClick

触摸事件冲突处理

上面介绍了事件分发的相关方法,下面就来讲讲怎么解决冲突;所谓冲突就是在触摸事件发生时不知道该由谁处理,这个判断逻辑该怎么怎么写,写在哪(其实重点是写在哪)

外部拦截法:

即在父View根据需求对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。

public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (满足父容器的拦截要求) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;
                break;
            }
            default:
                break;
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }
  • ACTION_DOWN 一定要返回false,不要进行拦截,否则根据事件分发机制,后续ACTION_MOVE与ACTION_UP事件都默认交给父View去处理!
  • 同上为了让子View可以接收到事件,ACTION_UP也需要返回false
内部拦截法

即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作(干预父View的对除了ACTION_DOWN之外的事件拦截)。

public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (父容器需要此类点击事件) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

父View需要从写onInterceptTouchEvent方法:

public boolean onInterceptTouchEvent(MotionEvent event) {

        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

需要注意的是:

  • 内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。
  • 滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。
  • 由于判断逻辑在子View的ACTION_MOVE中,所以在ACTION_DOWN时需要调用parent.requestDisallowInterceptTouchEvent(true)阻止父View对ACTION_MOVE的拦截

你可能感兴趣的:(Android View事件分发)