• Android 事件传递机制


    感谢大佬:https://www.jianshu.com/p/828550a1a0bf
    感谢大佬:https://www.cnblogs.com/shakinghead/p/10685148.html
    感谢大佬:https://blog.csdn.net/lbcwnu/article/details/82217907

    简略版

    1.控件的Listener事件触发的顺序是先onTouch,再onClick。
    2.控件的onTouch返回true,将会onClick事件没有了—阻止了事件的传递。
    返回false,才会传递onClick事件(才会传递up事件)


    补充:

    Android View的事件传递及滑动冲突

    ##事件传递顺序

    当用户点击屏幕产生一个事件,事件通过底层硬件捕获,然后交给ViewRootImpl处理,ViewRootImpl通过Window将事件交给Activity。
    事件要传递给Activity那么它就必须持有Activity的引用,Window在Activity的attach方法中通过mWindow.setCallback(this)调用持有了Activity的引用,Activity实现了Window.Callback的接口方法。所以最终事件是通过Window.Callback.dispatchTouchEvent把时间交给Acitivity的。

    /**
     * API from a Window back to its caller.  This allows the client to
     * intercept key dispatching, panels and menus, etc.
     */
    public interface Callback {
    
        public boolean dispatchKeyEvent(KeyEvent event);
    
        public boolean dispatchKeyShortcutEvent(KeyEvent event);
        
        public boolean dispatchTouchEvent(MotionEvent event);
        ...........
    }
    
    

    事件传递顺序
    硬件 -> ViewRootImpl -> Window -> Activity -> PhoneWindow -> DecorView -> VIewGroup -> View
    事件传递中主要的三个方法dispatchTouchEvent() onInterceptTouchEvent() 和 onTouchEvent()

    ##View的事件处理流程

    Activity的事件处理流程

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    

    getWindow()即为PhoneWindow,通过跟踪源码可以看到PhoneWinodw调用了DectorView最后到ViewGroup,最终调用的是ViewGroup.superDispatchTouchEvent(ev)。

    Activity的事件传递流程 Activity -> PhoneWindow -> DectorView -> ViewGroup
    如果ViewGoup处理了事件则dispatchTouchEvent就结束了
    如果ViewGroup没有处理事件则由Activity.onTouchEvent方法自己处理

    在这里插入图片描述

    ViewGroup的事件处理流程图

    在这里插入图片描述

    View的事件处理流程图

    在这里插入图片描述

    ##View的滑动冲突

    当我们内外两层View都可以滑动时候,就会产生滑动冲突。滑动冲突有两种形式,内外两层滑动方向不一致和滑动一致。不管是哪种形式,我们只需要根据我们的逻辑考虑什么时候需要外层View处理滑动,什么时候需要内层View处理滑动即可。

    滑动冲突处理的方式有两种,外部拦截法和内部拦截法。

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

    根据业务逻辑需要,在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理。
    ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE 与
    ACTION_UP事件都将默认交给父View去处理
    ACTION_UP也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发
    外部拦截法子View不需要做任何处理

    ####内部拦截法
    内部拦截法父View拦截除ACTION_DOWN以外的其它事件。子View在ACTION_DOWN中调用getParent().requestDisallowInterceptTouchEvent(true)方法接管事件并在ACTION_MOVE中根据业务逻辑决定事件是否教给父View处理。如需交给父View处理则调用requestDisallowInterceptTouchEvent(false)方法。内部拦截法不符合事件分发流程,是通过子VIew反向控制父View拦截。伪代码如下:

    /**
     * 内部拦截法
     * 父View需拦截除DOWN以外的其他事件
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            super.onInterceptTouchEvent(ev);
            return false;
        } else {
            return true;
        }
    }
    
    //子View.dispatchTouchEvent
    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;
        }
    }
    
    

    上述代码是内部拦截的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要做改动而且也不能有改动。

    父元素要默认拦截除了ACTION_DOWN以外的其他事件
    子元素调用parent.requestDisallowInterceptTouchEvent(false/true)来控制父元素是否拦截事件
    父元素不能拦截ACTION_DOWN因为它不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View


    补充:Android事件传递、多点触控及滑动冲突的处理 https://www.jianshu.com/p/7e21896a2c67

  • 相关阅读:
    第1次作业
    第0次作业
    总结报告
    第14、15周作业
    第七周作业
    第六周作业
    第四周作业
    第四次作业
    第三次作业
    2018第二次作业
  • 原文地址:https://www.cnblogs.com/tfxz/p/12621627.html
Copyright © 2020-2023  润新知