关于android:Android事件分发基础原理和场景分析

1次阅读

共计 10266 个字符,预计需要花费 26 分钟才能阅读完成。

作者:京东批发 郭旭锋

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 的呢。咱们先看看源码

# Activity
public 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
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);
}


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

# DecorView
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);
    }
}

# FrameLayout
public class FrameLayout extends ViewGroup {
}

# ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {public boolean dispatchTouchEvent(MotionEvent ev) {......}
}

# View
public 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 中的相干办法,如下

# View
public 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 代码

# ViewGroup
public 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 办法进行解决

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

正文完
 0