Android事件分发/传递机制总结


参考资料:
Android事件传递机制
Android事件分发机制(郭霖)
Android触摸屏事件派发机制详解与源码分析一(View篇)–工匠若水
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)–工匠若水
Android事件分发机制详解:史上最全面、最易懂
极客学院
Android开发艺术探索

View层级图
Android事件分发/传递机制总结_第1张图片


了解Android事件分发/传递机制是学习自定义控件的基础,所以绝对有必要通过阅读源码来深入理解事件分发的机制和流程,在此综合各个渠道的资料,对事件分发做了一个总结归纳,仅作为个人学习用途。感谢互联网的开放,感谢所有前辈。

方法介绍

// View:// 进行事件分发public boolean dispatchTouchEvent(MotionEvent event)// 处理点击事件public boolean onTouchEvent(MotionEvent event)
// ViewGroup:public boolean dispatchTouchEvent(MotionEvent event)public boolean onTouchEvent(MotionEvent event)// 拦截事件public boolean onInterceptTouchEvent(MotionEvent ev)

事件分发的流程可以用如下伪代码来描述:

public boolean dispatchTouchEvent (MotionEvent ev) {    boolean consume = false;    if (onInterceptTouchEvent(ev)) {        consume = onTouchEvent(ev);    } else {        consume = child.dispatchTouchEvent(ev);    }    return consuem;}

事件的分发顺序:Activity->Window->ViewGroup->View,所以我们先来分析Activity的事件分发过程。

Activity的事件分发过程

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}
  1. 第3行onUserInteraction()这个回调函数经常和onUserLeaveHint()一起被调用,用来响应Activity运行时用户点击通知的相关操作。
  2. 所以,事件会先交给Activity所附属的Window进行分发。如果返回true,表示有子View消费了事件,则事件循环结束;如果返回false,表示没有子View消费事件,则事件会交给Activity的onTouchEvent来处理。
  3. getWindow()获得的Window是一个抽象类,它的实现类是PhoneWindow。通过一系列调用,最后走到了ViewGroup的dispatchTouchEvent()方法。
PhoneWindow.java@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {    boolean handled = mDecor.superDispatchTouchEvent(event);    return handled;}DecorView.javapublic boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}ViewGroup.java@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {}

Android View的事件分发机制总结

首先从简单一点的View的事件分发机制入手,ViewGroup多了一个onInterceptTouchEvent()方法。

  • dispatchTouchEvent—->onTouch—->onTouchEvent—->PerformClick—->onClick。
  • 如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调用onTouchEvent。
  • 如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理。
  • 如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。
  • 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action(也就是说dispatchTouchEvent返回true才会进行下一次action派发)。
  • 正常情况下,一个事件序列(down开始,up结束)只能被一个View拦截且消耗。也就是说View要对一个事件序列负责到底。
  • 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。
  • 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件,那么同一序列中的其他事件都不会再交给它处理,而是将事件交给父元素去处理。也就是说某个任务的首要事件处理不好,上级就不会把该任务的其它事件再交给你了。
  • 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续事件,最终这些消失的点击事件会传递给Activity处理.
  • View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)

Button和ImageView关于事件传递的区别:
1. Button默认是clickable,ImageView/TextView默认不是clickable
2. ImageView设置click监听之后,会变成clickable
3. 由于ImageView是不可点击的,所以在onTouchEvent中默认不会消耗事件(return false),从而dispatchTouchEvent不会派发下一次事件
4. 在ImageView的onTouch方法中返回true,则会跳过onTouchEvent,dispatchTouchEvent收到了true,进而派发下一次事件。

View—->dispatchTouchEvent:

// 关键代码public boolean dispatchTouchEvent(MotionEvent event) {    ...    if (onFilterTouchEventForSecurity(event)) {        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;        }    }    ...    return result;}

View—->onTouchEvent:

// 关键代码:public boolean onTouchEvent(MotionEvent event) {    ...    if ((viewFlags & ENABLED_MASK) == DISABLED) {        ...        return (((viewFlags & CLICKABLE) == CLICKABLE                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);    }    ...    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {        switch (action) {            case MotionEvent.ACTION_UP:                ...                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                    ...                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                       ...                        if (!focusTaken) {                           ...                            if (mPerformClick == null) {                                mPerformClick = new PerformClick();                            }                           ...                        }                    }                    ...                break;            case MotionEvent.ACTION_DOWN:                ...                break;            case MotionEvent.ACTION_CANCEL:               ...                break;            case MotionEvent.ACTION_MOVE:                ...                break;        }        return true;    }    return false;}

ViewGroup的事件分发总结:

  • Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。
  • 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,那么后续的move, up等事件将直接传递给该ViewGroup的onTouchEvent()处理,目标view将接收不到任何事件。返回false代表不对事件进行拦截,默认返回false。
  • 子View中如果将传递的事件消费掉(onTouchEvent()返回true),ViewGroup中将无法接收到任何事件。如果事件未被消费掉,则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件(实际上调用的就是子元素的DispatchTouchEvent方法,和普通View的事件分发一样)

ViewGroup—->dispatchTouchEvent:

  • N16:
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)这一条判断语句说明当事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立。否则if不成立,将intercepted设置为true,也即拦截事件。当事件为ACTION_DOWN或者mFirstTouchTarget不为null时判断disallowIntercept(禁止拦截)标志位,而这个标记在ViewGroup中提供了public的设置方法,即requestDisallowInterceptTouchEvent(boolean disallowIntercept)
  • N20:
    默认的onInterceptTouchEvent方法只是返回了一个false,也即intercepted=false。
  • N39:
    调用方法dispatchTransformedTouchEvent()将Touch事件传递给特定的子View。该方法十分重要,在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法。dispatchTransformedTouchEvent方法如果返回true则表示子View消费掉该事件。如果返回false,即子View未消费掉该事件(即onTouchEvent返回false),那么就不满足该if条件,也就无法执行addTouchTarget(),从而导致mFirstTouchTarget为null(因为mFirstTouchTarget一进来是ACTION_DOWN就置位为null了),那么该子View就无法继续处理ACTION_MOVE事件和ACTION_UP事件(16行的判断为false,也即intercepted=true了,所以之后一系列判断无法通过)。
  • N57:
    if判断的mFirstTouchTarget为null时,也就是说Touch事件未被消费,即没有找到能够消费touch事件的子组件或Touch事件被拦截了,则调用ViewGroup的dispatchTouchEvent()方法处理Touch事件(和普通View一样)。
  • N62:
    if判断的mFirstTouchTarget不为null时,也就是说找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View。可以看见在源码的else中对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()方法来实现的处理。

到此ViewGroup的dispatchTouchEvent方法分析完毕。

public boolean dispatchTouchEvent(MotionEvent ev) {    ...    if (onFilterTouchEventForSecurity(ev)) {        final int action = ev.getAction();        final int actionMasked = action & MotionEvent.ACTION_MASK;        // Handle an initial down.        if (actionMasked == MotionEvent.ACTION_DOWN) {            ...            cancelAndClearTouchTargets(ev);            resetTouchState();        }        // 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);                /// M : add log to help debugging                ...                ev.setAction(action); // restore action in case it was changed            } else {                intercepted = false;            }        } else {            ...            intercepted = true;        }        ...        TouchTarget newTouchTarget = null;        boolean alreadyDispatchedToNewTouchTarget = false;        if (!canceled && !intercepted) {           ...                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                            // Child wants to receive touch within its bounds.                           ...                            newTouchTarget = addTouchTarget(child, idBitsToAssign);                            alreadyDispatchedToNewTouchTarget = true;                            break;                        }                        ...                    }                }                ...            }        }        // Dispatch to touch targets.        if (mFirstTouchTarget == null) {            ...            // No touch targets so treat this as an ordinary view.            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;                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                    handled = true;                } else {                    final boolean cancelChild = resetCancelNextUpFlag(target.child)                            || intercepted;                    if (dispatchTransformedTouchEvent(ev, cancelChild,                            target.child, target.pointerIdBits)) {                        handled = true;                    }                    ...                }                predecessor = target;                target = next;            }        }        ...    return handled;}

更多相关文章

  1. Android中的Parcel机制 实现Bundle传递对象
  2. Android事件分发机制 详解攻略
  3. Android夸进程通信机制七:使用 Socket进行进程间通信
  4. android内存处理机制
  5. Android 中三种使用线程的方法
  6. Android触控事件
  7. Android 支持多屏幕机制
  8. 防止事件导致的oncreate的多次调用
  9. Android Activity onConfigurationChanged()方法 监听状态改变

随机推荐

  1. 【Android】小白进阶之接口和抽象类的使
  2. 获取Android开机启动项列表
  3. Retrofit使用总结
  4. Android中文API(99)—— RelativeLayout
  5. Android中IntentService的使用及其源码解
  6. Android采用Junit进行应用单元测试
  7. Android Debug keystore系统位置
  8. Android中的四种动画效果
  9. monoTouch for android visual studio c#
  10. android:editable is deprecated: Use an