美文网首页Android自定义View
Android View事件分发

Android View事件分发

作者: 王灵 | 来源:发表于2019-03-11 12:35 被阅读0次

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

    首先我们来认识下事件分发中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事件分发

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