作者:京东批发 郭旭锋

1 为什么须要事件散发

和其余平台相似,Android 中 View 的布局是一个树形构造,各个 ViewGroup 和 View 是按树形构造嵌套布局的,从而会呈现用户触摸的地位坐标可能会落在多个 View 的范畴内,这样就不晓得哪个 View 来响应这个事件,为了解决这一问题,就呈现了事件散发机制。

2 事件散发的要害办法

Android 中事件散发是从 Activity 开始的,能够看看各组件中事件散发的要害办法

Activity:没有 onInterceptTouchEvent 办法,因为如果 Activity 拦挡事件,将导致整个页面都没有响应,而 Activity 是零碎利用和用户交互的媒介,不能响应事件显然不是零碎想要的后果。所以 Activity 不须要拦挡事件。

ViewGroup:三个办法都有,Android 中 ViewGroup 是一个布局容器,能够嵌套多个 ViewGroup 和 View,事件传递和拦挡都由 ViewGroup 实现。

View:事件传递的最末端,要么生产事件,要么不生产把事件传递给父容器,所以也不须要拦挡事件。

3 事件散发流程剖析

3.1 事件散发流程概览

Activity 并不是一个 View,那么 Activity 是如何将事件散发到页面的 ViewGroup 和 View 的呢。咱们先看看源码

# Activitypublic boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    // 调用 Window 对象的办法,开始事件散发    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    // 如果事件散发返回 false,也即事件没被生产,则调用本人的 onTouchEvent 办法    return onTouchEvent(ev);}public boolean onTouchEvent(MotionEvent event) {    if (mWindow.shouldCloseOnTouch(this, event)) {        finish();        return true;    }    return false;}

能够看到,Activity 中的事件散发办法 dispatchTouchEvent 调用了 getWindow().superDispatchTouchEvent(ev) 办法,而这里的 WIndow 实际上是 PhoneWindow。

简略来说,Window 是一个抽象类,是所有视图的最顶层容器,视图的外观和行为都归他管,无论是背景显示、标题栏还是事件处理都是他治理的领域,而 PhoneWindow 作为 Window的惟一亲儿子(惟一实现类),天然就是 View 界的皇帝了。

下来看看 PhoneWindow 的代码

# PhoneWindow@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {    return mDecor.superDispatchTouchEvent(event);}

PhoneWindow 中又调用了 mDecor.superDispatchTouchEvent(event) 办法。mDecor 是 DecorView 对象,再看看 DecorView 的代码

# DecorViewpublic class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {    public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }}# FrameLayoutpublic class FrameLayout extends ViewGroup {}# ViewGrouppublic abstract class ViewGroup extends View implements ViewParent, ViewManager {    public boolean dispatchTouchEvent(MotionEvent ev) {        ......    }}# Viewpublic class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {    public boolean dispatchTouchEvent(MotionEvent ev) {        ......    }}

能够看到,DecorView 实际上就是 ViewGroup,事件散发办法最终调用到了 ViewGroup 的 dispatchTouchEvent(MotionEvent ev) 办法。

DecorView 是 PhoneWindow 的一个对象,其职位就是跟在 PhoneWindow 身边业余为 PhoneWindow 服务的,除了本人要干活之外,也负责音讯的传递,PhoneWindow 的批示通过 DecorView 传递给上面的 View,而上面 View 的信息也通过 DecorView 回传给 PhoneWindow。

Android 中的事件散发是责任链模式的一种变形。事件由上往下传递,如果事件没有被生产则持续传递到下一层,如果事件被生产则进行传递,如果到最上层事件则没有被生产,则事件会层层传递给上一层解决。咱们都晓得事件散发的源头在 Activity 中的 dispatchTouchEvent 办法中,事件从这里开始,散发到布局中的各个 View 中,一直递归调用 ViewGroup/View 的 dispatchTouchEvent 办法。通过下面剖析能够看到,Activity 在承受到下层派发来的事件后,会把事件传递到本人的 dispatchTouchEvent 办法中,而后Activity 会把触摸、点击事件传递给本人的 mWindow 对象,最终传递给 DecorView 的 dispatchTouchEvent 办法,理论调用的是 ViewGroup 的 dispatchTouchEvent 办法。

3.2 事件散发源码剖析

通过剖析,能够晓得 Android 中事件散发的要害办法就是 ViewGroup 和 View 中的相干办法,如下

# Viewpublic class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {    public boolean dispatchTouchEvent(MotionEvent event) {        // ... 省略局部代码        boolean result = false;        // ... 省略局部代码        if (onFilterTouchEventForSecurity(event)) {            // ... 省略局部代码            // 1. 次要调用 onTouchEvent 办法,返回 true 阐明事件被生产,否则没被生产            if (!result && onTouchEvent(event)) {                result = true;            }        }        // ... 省略局部代码        return result;    }        public boolean onTouchEvent(MotionEvent event) {        // ... 省略局部代码        // 2. 默认可点击则返回 true,也就是生产事件。Button 或设置过 OnClickListener,则 View 可点击        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {            switch (action) {                case MotionEvent.ACTION_UP:                    // ... 省略局部代码                    break;                case MotionEvent.ACTION_DOWN:                    // ... 省略局部代码                    break;                case MotionEvent.ACTION_CANCEL:                    // ... 省略局部代码                    break;                case MotionEvent.ACTION_MOVE:                    // ... 省略局部代码                    break;            }            return true;        }        return false;    }}

View 中的办法逻辑比较简单,如备注 1 所示,dispatchTouchEvent 次要就是做一些安全检查,查看通过后会调用 onTouchEvent 办法。而 onTouchEvent 办法中逻辑如备注 2 所示,如果 View 是可点击的,则默认会认为生产事件,否则不生产,个别 Button 控件,或设置过 OnClickListener 的控件,View 会被默认设置为可点击。

上面看看 ViewGroup 代码

# ViewGrouppublic abstract class ViewGroup extends View implements ViewParent, ViewManager {    public boolean dispatchTouchEvent(MotionEvent ev) {        // ... 省略局部代码        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // 1. 如果是 DOWN 事件,则重置事件状态            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }            final boolean intercepted;            // 2. 如果是 DOWN 事件,会判断以后 ViewGroup 是否要拦挡事件。这里受两个因素影响:            //    一是 FLAG_DISALLOW_INTERCEPT,如果设置不拦挡,则不会调用 onInterceptTouchEvent,间接设置为不拦挡            //    二是没设置 FLAG_DISALLOW_INTERCEPT 标记,默认容许拦挡,会调用 onInterceptTouchEvent 办法            // 3. 如果不是 DOWN 事件,可能是 MOVE 或 UP 事件,mFirstTouchTarget 是记录须要持续进行事件散发的下一级子 View,包含ViewGroup 或 View,这里也分为两种状况            //    如果 mFirstTouchTarget 不为空,阐明须要持续向下一级子 View/ViewGroup 散发事件,这时阐明上次 DOWN 事件找到了上级有生产事件的子 View,且无拦挡事件            //    如果 mFirstTouchTarget 为空,阐明没找到要生产事件的子 View,或事件被拦挡了            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;            }            // ... 省略局部代码            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            // 4. 上面逻辑次要就是遍历寻找能生产事件的 View,如果事件被拦挡,则不须要再寻找            if (!canceled && !intercepted) {                // ... 省略局部代码                // 5. 只有 DOWN 事件才须要寻找,其余事件时曾经确定是否找到,都不须要再找生产事件的 View 了                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    // ... 省略局部代码                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        // ... 省略局部代码                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            // ... 省略局部代码                            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);                            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);                            // 6. 这个办法是要害                            //    如果 child 不为空,则会再调用 child.dispatchTouchEvent 办法,达到层层递归的成果                            //    如果 child 为空,则会调用 super.dispatchTouchEvent 办法,super 是 View,实际上调用了 onTouchEvent 办法,本人判断是否生产事件                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // ... 省略局部代码                                // 7. 返回 true,阐明找到了生产事件的 View,上面办法会给 mFirstTouchTarget 赋值,上面 mFirstTouchTarget 将不为空                                //    注:mFirstTouchTarget 并不是最终生产事件的 View,而是下一级蕴含生产事件 View 的链表对象,或是间接生产事件的 View 的链表对象                                //    每一个 ViewGourp 都会记录一个 mFirstTouchTarget,mFirstTouchTarget.child 记录了下一层生产事件的 ViewGroup 或 View                                //    同时,alreadyDispatchedToNewTouchTarget 变量会设置为 true                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                            // ... 省略局部代码                        }                        // ... 省略局部代码                    }                    // ... 省略局部代码                }            }            // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // 8. 当没有找到生产事件的 View,或事件被拦挡,mFirstTouchTarget 都不会被赋值,这里 child 为空,会调用本人的 onTouchEvent 办法                handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;                    // 9. 阐明找到了生产事件的 View,并且曾经散发,间接设置为已解决                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;                        // 10. 此办法和备注 6 和 8 都一样,这里多了 cancel 的解决逻辑。如果事件被拦挡,须要给原来生产事件的 View 发一个 CANCEL 事件                        if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {                            handled = true;                        }                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }            // ... 省略局部代码        }        // ... 省略局部代码        return handled;    }        public boolean onInterceptTouchEvent(MotionEvent ev) {        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)                && ev.getAction() == MotionEvent.ACTION_DOWN                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)                && isOnScrollbarThumb(ev.getX(), ev.getY())) {            return true;        }        // 默认不拦挡        return false;    }        // 没有覆写这个办法,理论调用的是 View 的 onTouchEvent 办法    public boolean onTouchEvent(MotionEvent event) {    }        }

能够看到,ViewGroup 中的事件散发逻辑还是比较复杂,但抓住关键点后则很容易能看清它的原本风貌

(1)散发的事件包含 DOWN、MOVE、UP、CANCEL 几种,用户一个残缺的动作就是由这几个事件组合而成的

(2)只有 DOWN 事件中会寻找生产事件的指标 View,其余事件不会再寻找

(3)DOWN 事件寻找到指标 View 后,后续其余事件都会间接散发至指标 View

(4)事件能够被拦挡,拦挡后原指标 View 会收到 CANCEL 事件,后续将不会再收到任何事件(这也是这套机制不反对丰盛的嵌套滑动的起因)

3.3 事件散发情景剖析

3.3.1 散发过程没有任何 View 拦挡和生产

(1)事件返回时,为了简化了解,dispatchTouchEvent 间接指向了父 View 的 onTouchEvent ,实际上它仅仅是返回给父 View 的 dispatchTouchEvent 一个 false 值(影响了 mFirstTouchTarget 的值),父 View 依据返回值来调用本身的onTouchEvent 办法

(2)ViewGroup 是依据 onInterceptTouchEvent 的返回值(影响了 mFirstTouchTarget 的值)确定是调用子 View 的 dispatchTouchEvent 还是本身的 onTouchEvent 办法

(3)如果所有 View 都没有生产 DOWN 事件,后续 MOVE 和 UP 不会再往下传递,会间接传递给 Activity 的 onTouchEvent 办法

3.3.2 最底层View生产事件,且下层View没有拦挡事件

(1)若没有 ViewGroup 对事件进行拦挡,而最底层 View 生产了此事件,也就是接管到 DOWN 事件时 View 的 onTouchEvent 返回 true,事件将不会再向上传递给各个 ViewGroup 的 onTouchEvent 办法,而是间接返回,后续的 MOVE 和 UP 事件也将会间接交给 View 进行解决

3.3.3 最底层View没有生产事件,ViewGroup2生产了事件,且下层View没有拦挡事件

(1)如果 View 没有生产事件,在层层调用父布局的 onTouchEvent 办法时,有 View 生产此事件,如 ViewGroup2 生产此事件,后续 MOVE 和 UP 事件将会传递给 ViewGroup2 的 onTouchEvent 办法,而且不会再调用 ViewGroup2 的 onInterceptTouchEvent 办法

(2)源码 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {} 这个代码中次要调用 onInterceptTouchEvent() 办法和解决是否拦挡

第一次是 DOWN 事件会进行判断,所以会调用 onInterceptTouchEvent 拦挡办法

第二次非 DOWN 事件,不会再调用 onInterceptTouchEvent 办法。起因如下:

◦ 如果 DOWN 事件的时候进行过拦挡,也就是 onInterceptTouchEvent() 办法返回 true,则 mFirstTouchTarget 必然为 null,不会调用 onInterceptTouchEvent 办法。因为前面不会对这个值赋值,会往下走逻辑,间接调用到此 View 或 ViewGroup 的 onTouchEvent() 办法

◦ 如果 DOWN 事件没有拦挡,但子 View 的 onTouchEvent 都返回 false,只有以后 ViewGroup 的 onTouchEvent 返回 true,mFirstTouchTarget 也同样为 null,也不会调用 onInterceptTouchEvent 办法。因为 mFirstTouchTarget 实质是找能接管事件的子 View,所有子 View 都不接管事件,mFirstTouchTarget 就必然为 null

3.3.4 ViewGroup2拦挡了并生产了DOWN事件,其余View没有拦挡事件

(1)ViewGroup2 拦挡 DOWN 事件后,View 不会接管到任何事件。ViewGroup2 生产事件后,后续 MOVE 和 UP 事件会交给 ViewGroup2 的 onTouchEvent 办法进行解决,且不会再调用 ViewGroup2 的onInterceptTouchEvent 办法

3.3.5 View生产了DOWN事件,ViewGroup2拦挡且生产了MOVE事件,其余View没有拦挡事件

(1)View 中 DOWN 事件失常传递

(2)当 ViewGroup2 拦挡 MOVE 事件后,以后 mFirstTouchTarget 不为空,首先 View 会收到转换后的 CANCEL 事件,mFirstTouchTarget 会置为空,下次 MOVE 事件因为 mFirstTouchTarget 为空,会调用到本人的 onTouchEvent 办法

3.3.6 View生产 DOWN 事件,ViewGroup2拦挡且生产了MOVE事件,肯定条件后,ViewGroup1再次拦挡和生产MOVE事件,其余View没有拦挡事件

3.4 事件散发总结

(1)整个散发过程中没有任何拦挡和生产,DOWN 事件会层层往下散发,并层层往上返回 false,MOVE 和 UP 事件则会交给 Activity 的 onTouchEvent 办法进行解决,不再往下散发

(2)散发过程中没有任何拦挡但有生产,DOWN 事件会层层往下散发,并层层往上返回false,直到有生产返回 true,MOVE 和 UP 事件则会层层往下散发,最初间接交给生产事件的 View 进行解决,而后层层返回 true

(3)散发过程中有拦挡且拦挡后生产,DOWN 事件会层层往下散发,直到有拦挡后间接交给生产的 View 进行解决,MOVE 和 UP 事件则会层层往下散发,最初间接交给生产事件的 View 进行解决,而后层层返回true

(4)散发过程中不拦挡 DOWN 事件,但拦挡 MOVE 事件且拦挡后生产,第一次拦挡,之前收到 DOWN 事件的子 View 会收到 CANCEL 事件,并层层返回;后续 MOVE 和 UP 会层层往下散发,最初间接交给生产事件的 View 进行解决

(5)散发过程中不拦挡 DOWN 事件,但拦挡 MOVE 事件且拦挡后不生产,第一次拦挡,之前收到 DOWN 事件的子 View 会收到 CANCEL 事件,并层层返回;后续 MOVE 和 UP 会层层往下散发,最初交给拦挡的 View 进行解决,此时因为拦挡的 View 没有生产,会层层往上返回 false,最初会交给 Activity 的 onTouchEvent 办法进行解决

以上,是集体的一些剖析和教训,欢送有趣味的小伙伴一起学习和探讨!