Android View | Android 事件分发机制

2020-08-04  本文已影响0人  南子李

1. 事件分发的对象

事件分发的对象是 MotionEvent,表示手指接触屏幕后所产生的一系列事件,包括以下常见事件:

ACTION_DOWN 手指刚接触屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手指从屏幕上松开的一瞬间

2. 事件分发的顺序

Activity -> Window -> DecorView

在 Android 中 Window 的唯一实现类是 PhoneWindow,从源码中了解到,PhoneWindow 将事件分发给了 DecorView,而 DecorView 继承自 FrameLayout,FrameLayout 继承自 ViewGroup,所以事件分发的顺序也可以表示为:

Activity -> PhoneWindow -> ViewGroup

3. 事件分发涉及的方法

分发事件,返回结果表示是否分发事件,是否分发由 onInterceptTouchEvent 或 onTouchEvent 的返回结果决定。

拦截事件,返回结果表示是否拦截事件。事件被拦截后将有当前 View 直接交给 onTouchEvent 方法消耗事件。

消耗事件,返回表示是否消耗事件。

伪代码表示三个方法之间的调用关系:
​public boolean dispatchTouchEvent(MotionEvent ev) { 
        boolean consume = false; ​ 
        if (onInterceptTouchEvent()) { 
              consume = onTouchEvent(ev); 
        } ​else { 
              consume = child.dispatchTouchEvent(ev);
 ​       } 
        return consume;
​}

4. 源码角度看事件分发

4.1 Activity 对点击事件的分发过程

事件的分发是从 Activity 开始的,首先我们来看在 Activity 中是如何实现事件分发的。(Dialog 也可以实现事件的分发)

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

点击事件产生时,Activity 在 dispatchTouchEvent() 方法中将点击事件传递给它的 Window 进行分发,如果返回 true,整个事件的循环分发结束,返回 false 表示没有 View 处理点击事件,即所有 View 的 onTouchEvent() 都返回了 false。我们来看 Window 的superDispatchTouchEvent()

    public abstract boolean superDispatchTouchEvent(MotionEvent event);

superDispatchTouchEvent() 是抽象方法,需要其子类去实现具体的分发过程,而在 Android 中,抽象类 Window 的唯一实现类是 android.view.PhoneWindow,接着来看 PhoneWindow 中对方法superDispatchTouchEvent() 的实现:

    public boolean superDispatchTouchEvent(MotionEvent event){
        return mDecor.superDispatchTouchEvent(event);
    }

PhoneWindow 的 superDispatchTouchEvent() 中继续调用了 DecorView(顶级 View)的 superDispatchTouchEvent(),让 DecorView 去分发这个点击事件。这里的 DecorView 就是在 Activity 中通过 getWindow().getDecorView() 返回的 View。
DecorView 继承自 FrameLayout 且 FrameLayout 继承自 ViewGroup,ViewGroup 继承自 View,简言之 DecorView 就是一个 View。

整个流程下来,事件就通过 Activity 实现了从 Activity 传到 Window,再由 Window 传递给了 View 的过程,注意这里的 View 就是我们的顶级 View(DecorView)

回到 dispatchTouchEvent() 中,如果 mWindow.shouldCloseOnTouch() 返回 false,那么将调用 onTouchEvent()

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

当所有 View 都不消耗当前的点击事件,即所有 View 的 onTouchEvent() 都返回 false 时 Activity#onTouchEvent() 被调用。

4.2 View 的事件分发

从前面的分析我们知道了 Activity 的事件分发流程,现在我们的点击事件已经从 Activity 传到了 DecorView,那么 DecorView 又是如何进行事件的分发和处理的呢?DecorView 继承自 FrameLayout,FrameLayout 继承自 VeiwGroup,从 ViewGroup 的 dispatchTouchEvent() 中看起:

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 为 false,intercepted = true 表示拦截事件,将导致 ViewGroup 的 onInterceptTouchEvent() 方法不再被调用,同一事件序列的其他事件都将交给它处理。
接着开始遍历所有子元素:

        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            ...
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                ...
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
            ...
        }

上述代码中看到,遍历到每一个子元素时调用了 dispatchTransformedTouchEvent():

        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }

可以看到在 dispatchTransformedTouchEvent() 中实际上调用了子元素的 disPatchTouchEvent(),这样就事件从容器传到子元素,子元素若仍是容器则采用同样的方式一层一层的将事件传递下去,直到整个 View 树。如果子元素的 dispatchTouchEvent() 返回了 true,执行 addTouchTarget(),完成对 mFirstTouchTarget 赋值:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

从代码中看出,TouchTarget 是一种单链表结构,mFirstTouchTarget 是否被赋值将直接影响到 ViewGroup 对事件的拦截策略,如果 mFirstTouchTarget 为 null,那么 ViewGroup 将拦截下所有的点击事件交由自己的 onTouchEvent() 处理。

4.3 View 对点击事件的处理过程

此处的 View 不是 ViewGroup,先看它的 dispatchTouchEvent():

        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }

先判断 View 是否有设置 OnTouchListener,如果 OnTouchListener 中的 onTouch() 返回 true, onTouchEvent() 就不会被调用,可见 onTouch() 的优先级高于 onTouchEvent()
View(这里不包含 ViewGroup) 不包含子元素,那么它就不需要处理事件的分发,所以只能它自己来处理事件:

    public boolean onTouchEvent(MotionEvent event) {
        ...
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            ...
            return true;
        }
        return false;
    }
上一篇下一篇

猜你喜欢

热点阅读