一个demo让你彻底理解Android触摸事件的并发

注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch

1. 触摸动作及事件序列

(1)触摸事件的动作

触摸动作一共有三种:ACTION_DOWN、ACTION_MOVE、ACTION_UP。当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件;若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是否是一个ACTION_MOVE动作的这个距离常量叫做TouchSlop,可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取。

(2)事件序列

当用户的手指接触屏幕,在屏幕上滑动,又离开屏幕,这个过程会产生一系列触摸事件:ACTION_DOWN-->若干个ACTION_MOVE-->ACTION_UP。这一系列触摸事件即为一个事件序列。

2. 触摸事件的分发

(1)概述

当产生了一个触摸时间后,系统要负责把这个触摸事件给一个View(TargetView)来处理,touch事件传递到TargetView的过程即为touch事件的分发。

触摸事件的分发顺序:Activity-->顶级View-->顶级View的子View-->. . .-->Target View

触摸事件的响应顺序:TargetView --> TargetView的父容器 --> . . . -->顶级View -->Activity

(2)toush事件分发的具体过程

a. Activity对touch事件的分发

当用户手指接触屏幕时,便产生了一个touch事件,封装了touch事件的MotionEvent最先被传递给当前Activity,Activity的dispatchTouchEvent方法负责touch事件的分发。分发touch事件的实际工作由当前Activity的Window完成,而Window会将touch事件传递给DecorView(当前用户界面顶级View)。Activity的dispatchTouchEvent方法代码如下:

1 public boolean dispatchTouchEvent(MotionEvent ev) {
2     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3         onUserInteraction();
4     }
5     if (getWindow().superDispatchTouchEvent(ev)) {
6         return true;
7     }
8     return onTouchEvent(ev);
9 }

根据以上代码可以知道,touch事件会交由Window的superDispatchTouchEvent进行分发,若这个方法返回true,意味touch事件的分发过程结束,返回false则说明经过层层分发,没有子View对这个事件进行处理,即所有子View的onTouchEvent方法都返回false(即这个touch事件没有被“消耗”)。这时会调用Activity的onTouchEvent方法来处理这个touch事件。

在Window的superDispatchTouchEvent方法中,首先会把touch事件分发给DecorView,因为它是当前用户界面的顶级View。Window的superDispatchTouchEvent方法如下:

1 public abstract boolean superDispatchTouchEvent(MotionEvent ev);

是个抽象方法,这个方法由Window的实现类PhoneWindow实现,PhoneWindow的superDispatchTouchEvent方法的代码如下:

1 public boolean superDispatchTouchEvent(MotionEvent ev) {
2     return mDecor.superDispatchTouchEvent(event);
3 }

由以上代码可得,PhoneWindow的superDispatchTouchEvent方法实际上是通过DecorView的superDispatchTouchEvent方法来完成自己的工作,也就是说,当前Activity的Window直接将这个touch事件传递给了DecorView。也就是说,目前touch事件已经经过了如下的分发:Activity-->Window-->DecorView。

b. 顶级View对touch事件的分发

经过Activity与Window的分发,现在touch事件已经被传递到了DecorView的dispatchTouchEvent方法中。DecorView本质上是一个ViewGroup(更具体的说是FrameLayout),ViewGroup的dispatchTouchEvent方法所做的工作可以分为如下几个阶段,第一个阶段的主要代码如下:

1 //Handle an initial down.
2 if (actionMasked == MotionEvent.ACTION_DOWN) {
3     //Throw away all previous state when starting a new touch gesture.
4     //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.
5     cancelAndClearTouchTargets(ev);
6     resetTouchState();
7 }

第一阶段的主要工作有俩:一是在第6行的resetTouchState方法中完成了对FLAG_DISALLOW_INTERCEPT标记的重置;二是第5行的cancelAndClearTouchTargets方法会清除当前MotionEvent的touch target。关于FLAG_DISALLOW_INTERCEPT标记和touch target,在下文会有相关说明。

第二阶段的主要工作是决定当前ViewGroup是否拦截本次的touch事件,主要代码如下:

 1 //Check for interception.
 2 final boolean intercepted;
 3 if (actionMasked == MotionEvent.ACTION_DOWM || mFirstTouchTarget != null) {
 4     final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 5     if (!disallowIntercept) {
 6         intercepted = onInterceptTouchEvent(ev);
 7         ev.setAction(action); //restore action in case it was changed
 8     } else {
 9         intercepted = false;
10     }
11 } else {
12     //There are no touch targets and this action is not an initial down so this view group continues to intercept touches.
13     intercept =true;
14 }

由以上代码我们可以知道,当一个touch事件被传递到ViewGroup时,会先判断这个touch事件的动作是否是ACTION_DOWN,如果这个事件是ACTION_DOWN或者mFirstTouchTarget不为null,就会根据FLAG_DISALLOW_INTERCEPT标记决定是否拦截这个touch事件。那么mFirstTouchTarget是什么呢?当touch事件被ViewGroup的子View成功处理时,mFirstTouchTarget就会被赋值为成功处理touch事件的View,也就是上面提高的touch target。

总结一下上述代码的流程:在子View不干预ViewGroup的拦截的情况下(上述代码中的disallowIntercept为false),若当前事件为ACTION_DOWN或者mFirstTouchTarget不为空,则会调用ViewGroup的onInterceptTouchEvent方法来决定最终是否拦截此事件;否则(没有TargetView并且此事件不是ACTION_DOWN),当前ViewGroup就拦截下此事件。 一旦ViewGroup拦截了某次touch事件,那么mFirstTouchTarget就不会被赋值,因此当再有ACTION_MOVE或是ACTION_UP传递到该ViewGroup时,mTouchTarget就为null,所以上述代码第3行的条件就为false,ViewGroup会拦截下来。由此可得到的结论是:一旦ViewGroup拦截了某次事件,则同一事件序列中的剩余事件也会它默认被拦截而不会再询问是否拦截(即不会再调用onInterceptTouchEvent)。

这里存在一种特殊情形,就是子View通过requestDisallowInterceptTouchEvent方法设置父容器的FLAG_DISALLOW_INTERCEPT为true,这个标记指示是否不允许父容器拦截,为true表示不允许。这样做能够禁止父容器拦截除ACTION_DOWN以外的所有touch事件。之所以不能够拦截ACTION_DOWN事件,是因为每当ACTION_DOWN事件到来时,都会重置FLAG_DISALLOW_INTERCEPT这个标记位为默认值(false),所以每当开始一个新touch事件序列(即到来一个ACTION_DOWN动作),都会通过调用onInterceptTouchEven询问ViewGroup是否拦截此事件。当ACTION_DOWN事件到来时,重置标记位的工作是在上面的第一阶段完成的。

接下来,会进入第三阶段的工作:

 1 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
 2 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 3 TouchTarget newTouchTarget = null;
 4 boolean alreadyDispatchedToNewTouchTarget = false;
 5 if (!canceled && !intercepted) {
 6     // 不是ACTION_CANCEL并且不拦截
 7     if (actionMasked == MotionEvent.ACTION_DOWN) {
 8           // 若当前事件为ACTION_DOWN则去寻找这次事件新出现的touch target
 9           final int actionIndex = ev.getActionIndex(); // always 0 for down
10
11           ...
12
13           final int childrenCount = mChildrenCount;
14           if (newTouchTarget == null && childrenCount != 0) {
15               // 根据触摸的坐标寻找能够接收这个事件的touch target
16               final float x = ev.getX(actionIndex);
17               final float y = ev.getY(actionIndex);
18
19               final View[] children = mChildren;
20               // 遍历所有子View
21               for (int i = childrenCount - 1; i >= 0; i--) {
22                   final int childIndex = i;
23                   final View child = children[childIndex];
24                   // 寻找可接收这个事件并且touch事件坐标在其区域内的子View
25                   if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
26                       continue;
27                   }
28
29                   newTouchTarget = getTouchTarget(child); // 找到了符合条件的子View,赋值给newTouchTarget
30                   if (newTouchTarget != null) {
31                       //Child is already receiving touch within its bounds.
32                       //Give it the new pointer in addition to ones it is handling.
33                       newTouchTarget.pointerIdBits |= idBitsToAssign;
34                       break;
35                   }
36                   resetCancelNextUpFlag(child);
37                   // 把ACTION_DOWN事件传递给子组件进行处理
38                   if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
39                       //Child wants to receive touch within its bounds.
40                       mLastTouchDownTime = ev.getDownTime();
41                       if (preorderedList != null) {
42                           //childIndex points into presorted list, find original index
43                           for (int j=0;j<childrenCount;j++) {
44                               if (children[childIndex]==mChildren[j]) {
45                                   mLastTouchDownIndex=j;
46                                   break;
47                               }
48                           }
49                       } else {
50                           mLastTouchDownIndex = childIndex;
51                       }
52                       mLastTouchDownX = ev.getX();
53                       mLastTouchDownY = ev.getY();
54                       //把mFirstTouchTarget赋值为newTouchTarget,此子View成为新的touch事件的起点
55                       newTouchTarget = addTouchTarget(child, idBitsToAssign);
56                       alreadyDispatchedToNewTouchTarget = true;
57                       break;
58                  }
59              }
60          }
61     }
62 }

当ViewGroup不拦截本次事件,则touch事件会分发给它的子View进行处理,相关代码从第21行开始:遍历所有ViewGroup的子View,寻找能够处理此touch事件的子View,若一个子View不在播放动画并且touch事件坐标位于其区域内,则该子View能够处理此touch事件,并且会把该子View赋值给newTouchTarget。

若当前遍历到的子View能够处理此touch事件,就会进入第38行的dispatchTransformedTouchEvent方法,该方法实际上调用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相关的代码如下:

1 if (child == null) {
2     handled = super.dispatchTouchEvent(event);
3 } else {
4     handled = child.dispatchTouchEvent(event);
5 }

若dispatchTransformedTouchEvent方法传入的child参数不为null,则会调用child(即处理touch事件的子View)的dispatchTouchEvent方法。若该子View的dispatchTouchEvent方法返回true,则dispatchTransformedTouchEvent方法也会返回true,则表示成功找到了一个处理该事件的touch target,会在第55行把newTouchTarget赋值给mFirstTouchTarget(这一赋值过程是在addTouchTarget方法内部完成的),并跳出对子View遍历的循环。若子View的dispatchTouchEvent方法返回false,ViewGroup就会把事件分发给下一个子View。

若遍历了所有子View后,touch事件都没被处理(该ViewGroup没有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup会自己处理touch事件,相关代码如下:

1 if (mFirstTouchTarget == null) {
2     handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
3 }

由以上代码可知,ViewGroup自己处理touch事件时,会调用dispatchTransformedTouchEvent方法,传入的child参数为null。根据上文的分析,传入的chid为null时,会调用super.dispatchTouchEvent方法,即调用父类的dispatchTouchEvent方法,父类的dispatchTouchEvent方法中又包含了对onTouchEvent方法的调用,根据多态,实际上会回过头来调用当前View的onTouchEvent方法。(关于这点的详细说明请参考这篇文章:

c. View(不包含ViewGroup)对touch事件的处理

View的dispatchTouchEvent方法的主要代码如下:

 1 public boolean dispatchTouchEvent(MotionEvent event) {
 2     boolean result = false;
 3     . . .
 4
 5     if (onFilterTouchEventForSecurity(event)) {
 6         //noinspection SimplifiableIfStatement
 7         ListenerInfo li = mListenerInfo;
 8         if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
 9                 && li.mOnTouchListener.onTouch(this, event)) {
10             result = true;
11         }
12
13         if (!result && onTouchEvent(event)) {
14             result = true;
15         }
16         . . .
17         return result;
18 }

由上述代码可知,View对touch事件的处理过程如下:由于View不包含子元素,所以它只能自己处理事件。它首先会判断是否设置了OnTouchListener,若设置了,会调用onTouch方法,若onTouch方法返回true(表示该touch事件已经被消耗),则不会再调用onTouchEvent方法;若onTouch方法返回false或没有设置OnTouchListener,则会调用onTouchEvent方法,onTouchEvent对touch事件进行具体处理的相关代码如下:

 1 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 2     switch (event.getAction()) {
 3         case MotionEvent.ACTION_UP:
 4             boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
 5             if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
 6                 . . .
 7                 if (!mHasPerformedLongPress) {
 8                     //This is a tap, so remove the longpress check
 9                     removeLongPressCallback();
10
11                     //Only perform take click actions if we were in the pressed state
12                     if (!focusTaken) {
13                         //Use a Runnable and post this rather than calling performClick directly.
14                         //This lets other visual state of the view update before click actions start.
15                         if (mPerformClick == null) {
16                             mPerformClck = new PeformClick();
17                         }
18                         if (!post(mPerformClick)) {
19                             performClick();
20                         }
21                     }
22                 }
23                 . . .
24             }
25             break;
26     }
27     . . .
28     return true;
29 }

由以上代码可知,只要View的CLICKABLE属性和LONG_CLICKABLE属性有一个为true(View的CLICKABLE属性和具体View有关,LONG_CLICKABLE属性默认为false,setOnClikListener和setOnLongClickListener会分别自动将以上俩属性设为true),那么这个View就会消耗这个touch事件,即使这个View处于DISABLED状态。若当前事件是ACTION_UP,还会调用performClick方法,该View若设置了OnClickListener,则performClick方法会在其内部调用onClick方法。performClick方法代码如下:

 1 public boolean performClick() {
 2     final boolean result;
 3     final ListenerInfo li = mListenerInfo;
 4     if (li != null && li.mOnClickListener != null) {
 5         playSoundEffect(SoundEffectConstants.CLICK);
 6         li.mOnClickListener.onClick(this);
 7         result = true;
 8     } else {
 9         result = false;
10     }
11     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
12     return result;
13 }

3. 结合demo详解事件分发的各种情况

注:本文的demo参考自以下博文: http://hukai.me/android-deeper-touch-event-dispatch-process/

首先我们来看一下本文所使用的demo的布局情况:

以此为例,触摸事件的分发及响应顺序如下:

  • 分发:MainActivity -> ParentLayout -> ChildLayout -> CustomButton
  • 响应:CustomButton -> ChildLayotu -> ParentLayout -> MainActivity

从上面我们可以看到,分发的顺序是自顶向下,而响应的顺序是自底向上。我们可以把触摸事件看做是一项任务,而MainActivity看做是一个公司的老总,CustomButton看做底层的员工,这样来理解以下的叙述会更加容易。我们运行demo,然后在CustomButton点击一下而后松开,然后观察Log输出。

在讲解正常情况前,我们先来看假如把CustomButton删去会发生什么。删去CustomButton后的布局如下:

我们点击一下内层的ChildLayout并松开,可以得到以下日志输出:

这种情况是ChildLayout没有子View,根据我们上面讲解的这段代码:

1 if (child == null) {
2     handled = super.dispatchTouchEvent(event);
3 } else {
4     handled = child.dispatchTouchEvent(event);
5 }

此时会调用super.dispatchTouchEvent(event)。对于ChildLayout来说,它的父类是LinearLayout,所以会调用LinearLayout的dispatchTouchEvent方法,我们知道dispatchTouchEvent方法中包含着对onTouchEvent方法的调用,那么在LinearLayout的dispatchTouchEvent方法中对onTouchEvent方法的调用实际上会调用ChildLayout的onTouchEvent方法(根据多态)。也就是说,当一个ViewGroup没有子View或所有的子View都没消耗触摸事件时,实际上它会调用自己的onTouchEvent方法。

接下来,我们再恢复删除的CustomButton,针对各个情况进行具体的讲解。

(1)正常情况

在本例中,触摸事件分发响应的正常情况就是CustomButton消耗了触摸事件,然后层层向上返回true,最后到了MainAcitivity那便知道这次触摸事件被成功消耗了。这就好比老总下发任务,经过各级经理下发到员工后,员工圆满完成了任务,然后逐级上报最后汇报给了老总。我们来看一下日志输出:

(2)MainActivity的dispatchTouchEvent方法中对ACTION_DOWN返回true

这种情况下,ACTION_DOWN传到MainActivity的dispatchTouchEvent方法时,直接返回了true(被丢弃),因此没有找到TargetView,则后续的ACITON_MOVE、ACTION_UP会直接交由MainActivity的onTouchEvent处理,而它的onTouchEvent默认会返回false(没有能力消耗触摸事件)。我们来看一下日志输出:

(3)ParentLayout的dispatchTouchEvent方法中对ACTION_DOWN返回true

这种情况下,也是无法找到TargetView。由于没有TargetView,后续的ACTION_MOVE和ACTION_UP就只能下发到ParentLayout层了,而它无法消耗触摸事件。我们来看一下日志输出:

需要注意的是,触摸事件回传时,只有ParentLayout层和Activity层能够收到onTouchEvent的回调(假如ParentLayout还有一个父View,则该父View收不到onTouchEvent回调)。

(4)Activity的dispatchTouchEvent方法中对ACTION_MOVE返回true

这种情况下,ACTION_DOWN和ACTION_UP会被正常下发并被消耗,而ACTION_MOVE会在MainActivity即停止分发,日志如下:

注意,这里由于滑动距离未达到TouchSlop,所以并没有产生ACTION_MOVE事件,关于对ACTION_MOVE的处理大家可以参考(2)中对ACTION_DOWN的处理(直接在MainActivity层被丢弃)。

(5)ParentLayout的onInterceptTouchEvent方法中对ACTION_DOWN返回true

这种情况下,ACTION_DOWN分发到ParentLayout时被拦截,则会交给ParentLayout的onTouchEvent处理,而由于未找到TargetView,后续的ACTION_MOVE、ACTION_DOWN事件便无从下发,在MainActivity层就会停止下发。注意到onInterceptTouchEvent方法与dispatchTouchEvent方法的区别,前者若对ACTION_DOWN返回true,则同一事件序列的后续事件便在MainActivity层终止下发;后者若对ACTION_DOWN返回true,后续ACTION_MOVE及ACTION_UP会在返回true的那层终止下发,且回传时只有返回true的那层以及MainActivity层的onTouchEvent方法会被回掉。日志输出如下:

(6)ChildLayout的onInterceptTouchEvent方法中对ACTION_DOWN返回true

这种情况下,ACTION_DOWN会交由ChildLayout的onTouchEvent方法处理,而它默认会返回false,于是开始回传,经过ParentLayout、MainAcitivity均无法消费该事件。后续的事件会在MainAcitivity层便停止下发。日志如下:

(7)ChildLayout的onInterceptTouchEvent方法中对ACTION_MOVE返回true

这种情况下,ACTION_DOWN会被正常分发到CustomButton并被消费,CustomButton会成为TargetView。而ACTION_MOVE会被ChildLayout拦截,这时会产生一个ACTION_CANCEL消息(表示后续事件过不来了),这个消息会被分发给CustomButton,CustomButton会消耗它。后面若还有ACTION_MOVE会交给ChildLayout的onTouchEvent,由于无法消耗,会返回false。后续的ACTION_UP只能分发到ChildLayout。这种情况要和拦截ACTION_DOWN区分开,由于有TargetView,所以ACTION_UP能传递到拦截ACTION_MOVE的那层而不是终止在MainAcitivity层。日志输出如下:

04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onInterceptTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [dispatchTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [onTouchEvent] -> ACTION_DOWN
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [onTouchEvent] return true
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [dispatchTouchEvent] return true
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return true
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return true
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onInterceptTouchEvent] -> ACTION_MOVE, return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [dispatchTouchEvent] -> ACTION_CANCEL
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [onTouchEvent] -> ACTION_CANCEL
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [onTouchEvent] return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [dispatchTouchEvent] return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return true
04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return true
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.948 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.948 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE
04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE
04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_UP
04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_UP
04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_UP
04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false
04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_UP
04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_UP
04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false
04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false
04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false
04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_UP
04-05 03:15:40.992 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false
04-05 03:15:40.992 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false

总结一下:若某一层的dispatchTouchEvent方法对ACTION_DOWN返回true,则被认为该层能够消耗触摸事件,因此后续的事件会传到该层;而某一层的onInterceptTouchEvent方法返回true的话,由于它的onTouchEvent返回false进而导致它的dispatchTouchEvent方法返回false,则该层会被认为不能消耗触摸事件,所以后续事件只会到Activity层。

4. 参考资料

(1)《Android开发艺术探索》

(2)Touch事件分发响应机制

时间: 2024-10-26 02:44:36

一个demo让你彻底理解Android触摸事件的并发的相关文章

一个demo让你彻底理解Android中触摸事件的分发

注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOWN.ACTION_MOVE.ACTION_UP.当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件:若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MO

iOS 和 Android 触摸事件传递

先看2篇文章,写得很好,都是咱们博客园的博文 ios 触摸事件传递 http://www.cnblogs.com/Quains/p/3369132.html android 触摸事件传递 http://www.cnblogs.com/superlcr/p/3946034.html 读完这2篇文章,可以发现ios和android对触摸事件的处理的大体思路是一致的,都是从根view开始,遍历检测子view,找到适合的view触发具体事件.2个平台都具备分发触摸事件,拦截触摸事件传递的机能. 下面盗用

Android触摸事件

简介: 做了一个语音发送UI的小demo. 按下显示语音窗口,根据音量调节UI音量显示,上划至窗口显示取消发送. 原理: 1:获取什么事件来执行操作: 给Button添加setOnTouchListener事件,获得触摸事件,在滑动事件中得到当前显示控件的坐标,然后根据当前触摸位置与坐标进行判断来确定是否取消.在触摸离开的事件中来确定是否处理发送的请求. 2:更新音量值: 在线程中得到录音的音量大小,然后用handler发送到activity中进行UI更新. 详解事件: 通过public boo

Android 触摸事件 点击事件的分发机制 详解

最近发现团队里有些员工在做一些自定义控件的时候感觉比较吃力.尤其是做触摸事件这种东西的时候.很多人对机制并不理解.因为百度出来的东西都太理论化了.确实不好理解. 今天带大家坐几个小demo.帮助理解一下. 先从简单的view 的事件分发机制开始解释. 我们首先自定义一个工程 package com.example.testtouch; import android.app.Activity; import android.os.Bundle; import android.util.Log; i

Android触摸事件机制

前言 随着科学技术的发展,智能手机早已成为我们当代人身边必不可少的"伙伴"之一,堪比对象女友.每天我们对着手机重复的做着点击.滑动操作,而手机则随着我们的操作给我们展示她的精彩. - 废话到此结束. 看到这里,即使不是作为移动端码农的你也应该知道触摸事件对手机(经典键盘机除外)的重要性了. 什么是触摸事件 顾名思义,就是触摸手机屏幕后产生的事件.这时候请你拿出手机,点击屏幕中的某个按钮(不要松手),移动一段距离,松手. 这个过程一般会产生如下几个事件: 点击(Down)事件 移动(Mo

Android触摸事件分发机制

Android中的事件分为按键事件和触摸事件,这里对触摸事件进行阐述.Touch事件是由一个ACTION_DOWN,n个ACTION_MOVE,一个ACTION_UP组成onClick,onLongClick,onScroll等事件.Android中的控件都是继承View这个基类的,而控件分为两种:一种是继承View不能包含其他控件的控件:一种是继承ViewGroup可以包含其他控件的控件,暂且称为容器控件,比如ListView,GridView,LinearLayout等. 这里先对几个函数讲

android触摸事件流程(一)

对于android的触摸事件,一直以来都有点模糊,所以决定搞搞清楚.这里一共分三部分来解决这个问题:第一部分:触摸事件是如何起源的.第二部分:view是如何处理触摸事件的.第三部分:viewgroup是如何分发和处理触摸事件的. 这一次先看第一部分:触摸事件是如何起源的. 要理解这个问题,首先应该知道下面三点: 每一个需要显示到手机上的视图最总都是通过WindowManager.addview()的方式来实现的,比如我们常用的activity/popwindow/状态栏/锁屏/来闹钟界面等! 在

android 触摸事件、点击事件的区别

要弄清楚这个问题只要理解Android对事件处理的所谓消费(consume)概念即可,一个用户的操作会被传递到不同的View控件和同一个控件的不同监听方法处理,任何一个接收并处理了该次事件的方法如果在处理完后返回了true,那么该次event就算被完全处理了,其他的View或者监听方法就不会再有机会处理该event了. onLongClick的发生是由单独的线程完成的,并且在ACTION_UP之前,而onClick的发生是在ACTION_UP后,因此同一次用户touch操作就有可能既发生onLo

Android触摸事件(一)-TouchEventHelper

文件夹 文件夹 概述 关于更新 2016-08-31 2016-06-20 关于单点触摸事件singleTouch 单击的两种方式 关于双击事件 双击事件的检測逻辑 双击事件触发的时机 关于多点触摸事件multiTouch 两点触摸中的移动事件 两点触摸事件的触发过程 两点触摸的事件 实现 变量定义 触摸事件流程 自己定义事件计时方案 自己定义事件触发区域 触摸事件处理规则 关于回调的方法 触摸事件处理源代码 双击事件的优化处理 怎样检測当次触摸事件的单击事件 怎样检測触发双击事件 辅助补充逻辑