Android 触摸事件分发机制?

Android系统分析之带着问题看事件分发机制

一 事件分发机制

1 什么是事件分发机制?

1.1 什么是事件?

答:当用户触摸屏幕时,每一次的点击,按下,移动,抬起等都是一个事件。事件类型包括:按下(ACTION_DOWN),移动(ACTION_MOVE),抬起(ACTION_UP),取消(ACTION_CANCEL)。

1.2 什么是事件分发机制呢?

答:因为 Android 每一个页面都是基于 Activity 进行实现的,一个 Activity 里面有若干个 ViewGroup 和 View 组成的;而事件分发机制就是某一事件从屏幕传递到各个具体的 View,由某个 View 来使用这一事件 (消费事件) 或者忽略这一事件(不消费事件),这整个过程

1.3 什么是事件分发的对象?

答:系统会把整个事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)封装为MotionEvent 对象,事件分发的过程就是MotionEvent 对象分发的过程

1.4 什么是事件序列?

答:从手指按下屏幕开始,到手指离开屏幕为止所产生的一系列事件。也就是说一个完整的事件序列是以 ACTION_DOWN 事件开始,ACTION_UP 事件结束,中间有无数的 ACTION_MOVE 事件(当然可以没有,那就时一个点击事件),如图:

2 事件分发机制的原因和场景?

2.1 为什么会有事件分发机制?

答:Android 的布局结构是树形结构,这就会导致一些 View 可能会重叠在一起,当我们手指点击区域在多个布局范围之内,此时会有多个布局可以响应点击事件,这时候该让哪个 view 来响应点击事件呢?这就是事件分发机制存在的意义。

2.2 事件分发运用在哪里?

答:(1)自定义 View;(2)解决项目中各种事件冲突问题:例如,ViewPager 与 TabLayout 打造头尾循环的 ViewPager;ListView 的 Item 点击事件为什么和 Button 会冲突呢?

3 事件最先是从哪里来的?

3.1 事件最先是分发给 Activity 的吗?

答:不是。当事件由硬件传递到 Framework 层时,最先是由 WMS 通过 ViewRootImpl 将事件分发给 ViewRootImpl 的属性 mView,也就是 DecorView当触摸事件到达 DecorView 后,先经过 Activity 分发后,又回到 DecorView 中,再一步步传递到内部的子 View 中的
-–> 详细分析请看我的语雀文章:Android Activity 显示原 (Window/DecorView/ViewRoot)?

每日一问 | 事件到底是先到 DecorView 还是先到 Window 的?

Input 系统—事件处理全过程

二 Activity 事件分发

1 Activity 事件分发的思想?

答:当事件开始触发时,会调用 Activity 的 dispatchTouchEvent() 方法,然后调用 Window 的 superDispatchTouchEvent() 方法,将触摸事件分发给继承自 FrameLayout 的实例 DecorView。

如果 ViewGroup 捕捉了该事件并返回 true,代表事件传递到了 ViewGroup 中;否则调用 Activity 本身的 onTouchEvent() 方法捕捉该事件。

1.1 画一个 Activity 事件分发的流程图?

1.2 通过源码分析下 Activity 事件分发的流程?

答:当事件开始触发时,会调用 dispatchTouchEvent() 方法,那看下对应的源码:

1.3 步骤 1 ACTION_DOWN 代表事件序列的开始

答:ACTION_DOWN 代表事件序列的开始,所以这里基本是 true。进入onUserInteraction() 这个方法,在源码中该方法为空方法。所以说当我们可以重写 onUserInteraction() 方法就可以达到监听整个事件序列的开始

public void onUserInteraction() {}

1.4 步骤 2 将触摸事件分发给 DecorView

答:调用了 Window 的 superDispatchTouchEvent() 方法,因为 Window 的唯一的实现类 PhoneWindow 类,它持有了一个继承自 FrameLayout 的实例 DecorView,实质调用了 DecorView 的 dispatchTouchEvent 方法,最后调用了 ViewGroup 的 dispatchTouchEvent 方法

如果 ViewGroup 捕捉了该事件并返回 true,代表事件传递到了 ViewGroup 中;否则调用 Activity 本身的 onTouchEvent() 方法捕捉该事件,并且直接返回 onTouchEvent 的结果。源码如下:

// Window类是抽象类,且PhoneWindow是Window类的唯一实现类
// superDispatchTouchEvent(ev)是抽象方法,返回的是一个Window对象
public abstract boolean superDispatchTouchEvent(MotionEvent event);
// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {     // PhoneWindow 将事件直接传递给了 DecorViewreturn mDecor.superDispatchTouchEvent(event);
}
// DecorView.java Activity最顶层的View
public boolean superDispatchTouchEvent(MotionEvent event) {// super.dispatchTouchEvent(event)方法,其实就应该是 ViewGroup 的 dispatchTouchEvent()return super.dispatchTouchEvent(event);
}

1.5 步骤 3:如果 ViewGroup 没有捕捉事件,就调用 Activity 本身的 onTouchEvent() 方法捕捉该事件

答:如果 ViewGroup 没有捕捉事件,就调用 Activity 本身的 onTouchEvent() 方法捕捉该事件,并且直接返回 onTouchEvent 的结果。无论返回值是什么,事件都要结束。

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

如果 mWindow.shouldCloseOnTouch(this, event) 返回结果为true,就将该 Activity finish 掉,并且返回 true;否则为 false

三 ViewGroup 事件分发

1 ViewGroup 事件分发的思想?

答:ViewGroup 是一组 View 的组合,在其内部有可能包含多个子 View,当手指触摸屏幕上时,手指所在的区域既能在 ViewGroup 显示范围内,也可能在其内部 View 控件上。

因此它内部的事件分发的重心是:处理当前 ViewGroup 和子 View 之间的逻辑关系:

  1. 检查当前 ViewGroup 是否需要拦截触摸事件?
  2. 是否需要将触摸事件继续分发给子 View?
  3. 根据 mFirstTouchTarget,将触摸事件重新分发给子 View?

1.1 画一个 ViewGroup 事件分发的流程图?

1.2 事件分发核心 dispatchTouchEvent

**
整个 View体系 之间的事件分发,实质上 dispatchTouchEvent 方法 就是一个大的递归函数。在这个递归的过程中会适时调用 onInterceptTouchEvent 来拦截事件,或者调用 onTouchEvent 方法来处理事件。

先从宏观角度,纵览整个 dispatch 的源码如下:

如代码中的注释,dispatch 主要分为 3 大步骤:

  • 步骤 1:判断当前 ViewGroup 是否需要拦截此触摸事件,如果拦截则此触摸事件不再会传递给子 View或者以 CANCEL 的方式通知子 View)。
  • 步骤 2:如果没有拦截,则将事件分发给子 View 继续处理。如果子 View 捕获了此次事件,则将捕获触摸事件的子 View 赋值给 mFirstTouchTarget
  • 步骤 3:根据 mFirstTouchTarget,将触摸事件重新分发给子 View

1.3 步骤 1 判断当前 ViewGroup 是否需要拦截此触摸事件,如果拦截则此触摸事件不再会传递给子 View

图中红框标出了是否需要拦截的条件

  • 如果事件为 DOWN 事件,则调用 onInterceptTouchEvent 进行拦截判断
  • 或者 mFirstTouchTarget 不为 null(事件类型是:后续的 MOVE、UP 等事件),代表已经有子 View 捕获了这个事件,则调用 onInterceptTouchEvent 进行拦截判断。(这也是,子 View 对触摸事件进行了捕获,则直接将当前以及后续的事件交给 mFirstTouchTarget 指向的子 View 进行处理的原因。)

注意:如果不进行拦截判断,直接就判断为需要拦截,intercepted = true。

如果当前 ViewGroup 并没有对事件进行拦截,则执行步骤 2;如果进行了拦截,进入步骤 3:child 为 null,最终会调用 super.dispatchTouchEvent 方法,实际上最终会调用自身的 onTouchEvent 方法,捕捉触摸事件。

1.4 步骤 2 如果没有拦截,则将事件分发给子 View 继续处理。如果子 View 捕获了此次事件,则将捕获触摸事件的子 View 赋值给 mFirstTouchTarget

仔细看上述的代码可以看出:

  • 图中 ① 处表明事件主动分发的前提是事件为 DOWN 事件
  • 图中 ② 处遍历所有子 View;
  • 图中 ③ 处判断事件坐标是否在子 View 坐标范围内,并且子 View 并没有处在动画状态;
  • 图中 ④ 处调用 dispatchTransformedTouchEvent 方法将事件分发给子 View,如果返回 true 就是代表子 View 捕获事件成功,则将捕获事件的子 View 赋值给 mFirstTouchTarget 保存,如下图。

1.5 步骤 3 根据 mFirstTouchTarget,将触摸事件重新分发给子 View

步骤 3 有 2 个分支判断。

  • 分支 1:如果此时 mFirstTouchTarget 为 null,说明在上述的事件分发中并没有子 View 对事件进行了捕获操作

    • 这种情况下,直接调用 dispatchTransformedTouchEvent 方法分发转换后的触摸事件,并传入 child 为 null,最终会调用 super.dispatchTouchEvent 方法,实际上最终会调用自身的 onTouchEvent 方法,捕捉触摸事件。自身 由父类实现 的 dispatchTouchEvent 返回 true 就是代表捕获触摸事件。
    • 也就是说:如果没有子 View 捕获处理触摸事件,ViewGroup 会通过自身的 onTouchEvent 方法进行处理。
  • 分支 2:mFirstTouchTarget 不为 null,说明在上面步骤 2 中有子 View 对触摸事件进行了捕获,则直接将当前以及后续的事件交给 mFirstTouchTarget 指向的子 View 进行处理

    • 这种情况下,直接调用 dispatchTransformedTouchEvent 方法分发转换后的触摸事件,并传入 child 为 target.child,最终会调用 child.dispatchTouchEvent 方法,实际上最终会调用子 View 的 onTouchEvent 方法,捕捉触摸事件。子 View 的 dispatchTouchEvent 返回 true 就是代表捕获触摸事件。
    • dispatchTransformedTouchEvent 方法中本步骤涉及到的部分,如下图:

2 ViewGroup 的问题合集

2.1 mFirstTouchTarget 有什么作用

答:mFirstTouchTarget 的部分源码如下:

可以看出其实 mFirstTouchTarget 是一个 TouchTarget 类型的链表结构。而这个 TouchTarget 的作用就是用来保存捕获了 DOWN 事件的 View,具体保存在上图中的 child 变量。在 1.5 步骤 3 中判断如果 mFirstTouchTarget 不为 null,则再次将事件分发给相应的 TouchTarget

可是为什么是链表类型的结构呢?因为 Android 设备是支持多指操作的,每一个手指的 DOWN 事件都可以当做一个 TouchTarget 保存起来

2.2 为什么 DOWN 事件特殊?

答:所有 触摸 事件都是从 DOWN 事件开始的,这是 DOWN 事件比较特殊的原因之一。另一个原因是 DOWN 事件的处理结果会直接影响后续 MOVE、UP 事件的逻辑

在步骤 2 和 3 中,只有 DOWN 事件会传递给子 View 进行捕获判断,一旦子 View 捕获成功,后续的 MOVE 和 UP 事件是通过遍历 mFirstTouchTarget 链表,查找之前接受 ACTION_DOWN 的子 View,并将触摸事件分配给这些子 View。也就是说后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。
**

2.4 如果要传递事件到子 View,为什么父容器不能拦截 ACTION_DOWN 事件,父容器必须返回 false?

答:父容器拦截了 ACTION_DOWN,那么后续的 MOVE UP 等事件均不会再调用 onInterceptTouchEvent() 方法,都会直接交给父容器处理,这时事件没法再传递给子 View 了

2.5 如果子 View 需要实现点击事件,为什么父容器不能拦截 ACTION_UP 事件,父容器必须返回 false?

答:如果拦截掉了 ACTION_UP,肯定会导致子 View 的点击事件无法被处理,因为一个点击事件从 ACTION_DOWN 开始,从 ACTION_UP 结束,二者缺一不可,最终在 ACTION_UP 事件中实现

2.6 如果遍历所有的子 View 后事件都没有被合适的处理,有哪两种情况?

答:1.是ViewGroup 没有子 View;2.是子 View 处理了点击事件,但是因为子 View 在 onTouchEvent 返回了 false,然后在 dispatchTouchEvent 中也返回 false。这两种情况下,ViewGroup 都会自己处理点击事件

四 View 事件分发

1 View 事件分发的思想?

答:View 是一个单纯的控件,不能再被细分,内部也并不会存在子 View,所以它的事件分发的重点在于:当前 View 如何去处理 触摸 事件,并根据相应的手势逻辑进行一些列的效果展示(比如滑动,放大,点击,长按等)

  1. 是否存在 TouchListener
  2. onTouchEvent 方法中判断:子 View 是否接收并处理触摸事件

1.1 画一个 View 事件分发的流程图?

1.2 事件分发核心 dispatchTouchEvent

整个 View 的事件分发,实质上 dispatchTouchEvent 方法 就是一个大的递归函数。在这个递归的过程中会适时调用 onTouchEvent 方法来处理事件。

先从宏观角度,纵览整个 dispatch 的源码如下:

如代码中的注释,dispatch 主要分为 3 大步骤:

  • 步骤 1:判断该 View 是否有相应焦点,有没被其他 View 遮挡,能否被触摸。
  • 步骤 2:是否存在 TouchListener.onTouch
  • 步骤 3:onTouchEvent 方法中判断:子 View 是否接收并处理触摸事件

1.3 步骤 1 判断该 View 是否有相应焦点,有没被其他 View 遮挡,能否被触摸

答:判断该 View 是否有相应焦点,有焦点才进行后面的事件分发

if (event.isTargetAccessibilityFocus()) {if (!isAccessibilityFocusedViewOrHost()) {return false;}event.setTargetAccessibilityFocus(false);
}

检查安全策略,判断当前 View 有没被其他 View 遮挡,能否被触摸。如果当前 View 没被其他 View 遮挡、能被触摸,才进行后面的事件分发。

if (onFilterTouchEventForSecurity(event)) {}

1.4 步骤 2 是否存在 TouchListener.onTouch -->请看 五-2-2.2

答:判断当前的操控方式是不是为鼠标操作,如果是鼠标操作,直接返回值 ture,表示了捕捉该事件。然后 –>请看 五-2-2.2
![image.png](https://img-blog.csdnimg.cn/img_convert/c08707682edb664fc9a57810107de2c4.png#align=left&display=inline&height=305&margin=[object Object]&name=image.png&originHeight=305&originWidth=504&size=18535&status=done&style=none&width=504)

1.5 步骤 3 onTouchEvent 方法中判断:子 View 是否接收并处理触摸事件?

答:

  • 结论1:可点击的禁用(DISABLED)视图仍会捕捉触摸事件,只是不响应事件
  • 结论2:可点击状态下,才会响应点击事件。在 MOVE 事件中执行 长按事件,在 UP 事件中执行 点击事件。因为 MOVE 事件先于 UP 事件,因此 长按事件 先于 点击事件 执行。而且只有不响应 长按事件,才会响应 点击事件
  • 结论3:点击事件执行时机,onTouch() --> onTouchEvent() --> onLongClick() --> onClick()。
// View.java
public boolean onTouchEvent(MotionEvent event) {// 核心1 --> 可点击 且 处于一个禁用状态final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if ((viewFlags & ENABLED_MASK) == DISABLED) {// 可点击的禁用(DISABLED)视图仍会捕捉触摸事件,只是不响应事件return clickable;}// 核心2 --> 可点击状态下,才会响应点击事件if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:// 如果没响应长按事件,才会响应点击事件if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// performClick(); li.mOnClickListener.onClick(View.this);   }case MotionEvent.ACTION_DOWN/ACTION_CANCEL:...case MotionEvent.ACTION_MOVE:// MOVE 事件先于 UP 事件,因此 长按事件 先于 点击事件 执行// hasPendingLongPressCallback() --> performLongClick()li.mOnLongClickListener.onLongClick(View.this)break;}// 捕获事件return true;}// 不捕获事件return false;}
}

五 验证事件分发机制

1 事件分发流程代码演示?

1.1 布局和自定义View

(1)定义如下布局文件:(设置 MyView 可点击)

(2)MyViewGroup 和 MyView 是两个自定义 View,以及 Actvitiy,它们的源码分别如下:


(3)页面效果如下:

1.2 子 View 的 onTouchEvent 返回 false,表示不对触摸事件进行捕获

(1)MyView 的 onTouchEvent 返回 false,表示 View 没捕获事件

(2)点击:按下->抬起 (递归思想)

(3)滑动:按下->滑动->抬起

(4)结论:上图中在 DOWN 事件中,MyViewGroup 的 onInterceptTouchEvent 被触发了;然后在子 View 的 dispatchTouchEvent 中返回 false,代表它没捕获这个 DOWN 事件,事件层层回传,最后在最顶层的 Activity 处理。这种情况下 子 View 没被添加到 父容器(MyViewGroup) 中的 mFirstTouchTarget 中,因此当前以及后续的 MOVE 和 UP 事件不再进行拦截判断,都会直接交给 Activity 进行处理。

1.3 子 View 的 onTouchEvent 返回 true,表示对触摸事件进行捕获

(1)MyView 的 onTouchEvent 返回 true,表示 View 捕获了事件

(2)滑动:按下->滑动->抬起

(3)结论:上图中在 DOWN 事件中,MyViewGroup 的 onInterceptTouchEvent 被触发了;然后在子 View 的 dispatchTouchEvent 中返回 true,代表它捕获了这个 DOWN 事件。这种情况下 **子 View 会被添加到 父容器(MyViewGroup) 中的 mFirstTouchTarget 中,因此当前以及后续的 MOVE 和 UP 事件都会经过 MyViewGroup 的 onInterceptTouchEvent 进行拦截判断,最后全都交给子 View(mFirstTouchTarget 指向的子 View) 进行处理。

1.4 总结下 onTouchEvent 返回值的作用:返回 false/true 对事件分发的影响?

答:onTouchEvent() 用于处理事件,返回值决定子 View 是否捕获了这个事件,而且决定了子 View 的 dispatchTouchEvent 的返回值。。。也就是说,子 View 在处理完触摸事件后,是否还允许触摸事件继续向上(父容器)传递。如果返回 true,则父容器不用操心,子 View 自己处理触摸事件;返回 false,则向上传递给父容器

2 基于 1 的代码演示,验证事件的其他情况?

2.1 onTouchEvent 可点击时(clickable 或 longClickable 其一为true)返回true,否则返回false

(1)定义如下布局文件:

**(2)MyView 继承 View 或 TextView,返回默认的 onTouchEvent **
继承 View 或 TextView:

结果:

结论: View 和 TextView 等的 clickable 默认为 false。而且 longClickable 默认为 false。

**(3)MyView 继承 View 或 TextView,同时设置 setOnClickListener ,返回默认的 onTouchEvent **

设置 onClickListener



setOnClickListener 源码

结论: View 和 TextView 设置 setOnClickListener 后把 clickable 修改为 true

**(4)MyView 继承 Button,返回默认的 onTouchEvent **


结论: Button 等的 clickable 默认为 true。而且 longClickable 默认为 false。

2.2 setOnTouchListener 的作用是什么?onTouch 返回 false/true 对执行 onTouchEvent 的影响?

(1)定义如下布局文件:

(2)onTouch 返回 false

结果:

结论:onTouch 方法返回 false,表示没捕捉了事件,继续传递事件,会调用 View 的 onTouchEvent 方法

(3)onTouch 返回 true


结果:

结论: onTouch 方法返回 true,表示捕捉了事件,不再继续传递事件,不会调用 View 的 onTouchEvent 方法

(4)原因:
如果 设置了 setOnTouchListener 且 onTouch 方法返回 true,则 result 为 true,那么 !result 值为 false,表示捕捉了事件,不会调用 View 的 onTouchEvent 方法;同理,没设置 setOnTouchListener 或者 onTouch 方法返回 false,…,会调用 View 的 onTouchEvent 方法。

3 容易被遗漏的 CANCEL 事件

3.1 原因分析

(1)在上面的步骤 3 中,继续向子 View 分发事件的代码中,有一段比较有趣的逻辑:

上图红框中表明已经有子 View 捕获了触摸事件,但是蓝色框中的 intercepted 变量又是 true。这种情况下,事件主导权会重新回到父视图 ViewGroup 中,并传递给子 View 的分发事件中传入一个 cancelChild 值是 true

(2)看一下 dispatchTransformedTouchEvent 方法的部分源码如下:


因为之前传入参数 cancel 为 true,并且 child 不为 null,最终这个事件会被包装为一个 ACTION_CANCEL 事件传给 child

3.2 什么情况下会触发这段逻辑呢?

在 DOWN 事件中,当父容器的 onInterceptTouchEvent 先返回 false,将事件分发给子 View,然后在子 View 的 dispatchTouchEvent 返回 true,表示子 View 捕获了事件。关键步骤就是在接下来的 MOVE 事件的过程中,父容器的 onInterceptTouchEvent 又返回 true,intercepted 被重新置为 true,此时上述逻辑就会被触发,子控件就会收到 ACTION_CANCEL 的触摸事件。

3.3 实际上有个很经典的例子可以用来演示这种情况

当在 Scrollview 中添加自定义 View 时,ScrollView 默认在 DOWN 事件中并不会进行拦截,onInterceptTouchEvent 先返回 false,事件会被传递给 ScrollView 内的子 View。只有当手指进行滑动并到达一定的距离开始滚动之后,onInterceptTouchEvent 返回 true,并触发 ScrollView 的滚动效果。当 ScrollView 进行滚动的瞬间,内部的子 View 会接收到一个 CANCEL 事件,并丢失触摸焦点

(1)比如以下布局:

(2)CancelView 和 MyScrollView 各是一个自定义的 View,其源码如下:(CancelView 的 onTouchEvent 返回 true,表示它会将接收到的触摸事件进行捕获消费。)



(3)上述代码执行后,当手指点击屏幕时 DOWN 事件会被传递给 CancelView,手指滑动屏幕将 ScrollView 上下滚动,刚开始 MOVE 事件还是由 CancelView 来消费处理,但是当 ScrollView 开始滚动时,CaptureTouchView 会接收一个 CANCEL 事件,并不再接收后续的 触摸 事件。具体打印 log 如下:

(4)结论
因此,我们平时自定义 View 时,尤其是有可能被 ScrollView 或者 ViewPager 嵌套使用的控件,不要遗漏对 CANCEL 事件的处理,否则有可能引起 UI 显示异常。

4 如何用外部拦截法解决滑动冲突?

答:外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要这个事件就拦截给它处理,需要重写 onIterceptTouchEvent 方法,模板如下:

// 自定义ViewGroup.java,继承 ViewGroup
public boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercepted = false;switch (ev.getAction()){// ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件case MotionEvent.ACTION_DOWN:intercepted = false;break;// ACTION_MOVE事件,根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回falsecase MotionEvent.ACTION_MOVE:if("父容器的点击事件"){intercepted = true;} else {intercepted = false;}break;// ACTION_UP事件,父容器必须要返回falsecase MotionEvent.ACTION_UP:intercepted = false;break;}return intercepted;
}

(1) 常见滑动冲突场景?
答:场景 1,**外部滑动方向与内部滑动方向不一致,比如 ViewPager 中包含 ListView。**场景 2,**外部滑动方向与内部滑动方向一致,比如 ScrollView 中包含 ListView。**场景 3,上面两种情况的嵌套

(2) 滑动冲突处理规则?
答:通过判断是水平滑动还是竖直滑动来判断到底应该谁来拦截事件,可以根据水平和竖直两个方向的距离差或速度差来做判断

(3) 验证 ViewGroup 事件拦截?
答:点击 InterceptView 上,事件没传递到 InterceptView,在 InterceptViewGroup 这一层被拦截,打印如下:

 // InterceptViewGroup这一层被拦截:点击了InterceptView上,事件没传递到InterceptView,打印如下InterceptActivity dispatchTouchEventInterceptViewGroup dispatchTouchEventInterceptViewGroup onInterceptTouchEvent                // 拦截InterceptViewGroup onTouchEvent MotionEvent.ACTION_DOWNInterceptActivity onTouchEventInterceptActivity dispatchTouchEventInterceptActivity onTouchEvent// InterceptViewGroup这一层不拦截:点击了InterceptView上,事件传递到InterceptView,打印如下InterceptActivity dispatchTouchEventInterceptViewGroup dispatchTouchEventInterceptViewGroup onInterceptTouchEventInterceptView dispatchTouchEventInterceptView onTouchEventInterceptViewGroup onTouchEvent MotionEvent.ACTION_DOWNInterceptActivity onTouchEventInterceptActivity dispatchTouchEventInterceptActivity onTouchEvent

5 如何用内部拦截法解决滑动冲突?

答:内部拦截法是指父容器不直接拦截任何事件,所有的事件都传递给子 View,由子 View 控制。**如果子 View 要捕捉此事件就直接捕捉掉,否则就交由父容器进行处理。这种方法和 Android 中的事件分发机制不一致,需要配合 getParent().requestDisallowInterceptTouchEvent 方法才能正常工作,**需要重写子 View 的 dispatchTouchEvent 方法,模板如下:

// 自定义View.java
public boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:getParent().requestDisallowInterceptTouchEvent(true);  // 不拦截break;case MotionEvent.ACTION_MOVE:if("父容器的点击事件"){getParent().requestDisallowInterceptTouchEvent(false);  // 拦截}break;case MotionEvent.ACTION_UP:break;}return super.dispatchTouchEvent(event);}

5.1 requestDisallowInterceptTouchEvent 方法干扰 ViewGroup 的事件分发的原理是什么?

答:因为在 ViewGroup 的 dispatchTouchEvent 方法中,根据 disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 判断当前事件是否允许被拦截。

而 requestDisallowInterceptTouchEvent 方法通过修改标志位 mGroupFlags,使 disallowIntercept 结果变化

Android系统分析之带着问题看事件分发机制相关推荐

  1. Android View系列(二):事件分发机制源码解析

    概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...

  2. 一文读懂Android View事件分发机制

    Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...

  3. 随我一起慢慢揭开Andoird里事件分发机制的神秘面纱

    转载好文章: http://blog.csdn.net/chunqiuwei/article/details/41084921 学了这么久的Android,面试也常被问到事件分发机制,但总感觉对这个机 ...

  4. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  5. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    <div id="container">         <div id="header">     <div class=&qu ...

  6. Android进阶必备:滑动冲突解决与事件分发机制(附视频讲解)这篇看完还不懂请寄刀片

    1.前言 Android学习一段时间,需求做多了必然会遇到滑动冲突问题,比如在一个ScrollView中要嵌套一个地图View,这时候触摸移动地图或者放大缩小地图就会变得不太准确甚至没有反应,这就是遇 ...

  7. Android面试老生常谈的 View 事件分发机制,看这一篇就够了

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 在 Android 开发当中,View 的事件分发机制是一块很重 ...

  8. Android开发指南!Android事件分发机制收藏这一篇就够了,2年以上经验必看

    前言 现在的终端开发已经开始进入稳定期,在这个阶段大厂压力很大小厂更会收到挤压,人们使用的App越来越固定,即使是大厂,几年前平台级应用发个Push就能引流几十万,现在这些的作用也越来越小,特别到了今 ...

  9. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

最新文章

  1. Python再夺冠,上古语言COBOL大流行,IEEE Spectrum 2020年度编程语言排行榜出炉!...
  2. R语言difftime函数计算时间差值实战
  3. windows下部署MySQL 8.0.11
  4. SQL Server FOR XML PATH 语句的应用
  5. Java中的字符串驻留
  6. python将csv文件导入mysql-使用python将csv文件导入Mysql数据库
  7. Python高级数据类型-字符串,列表,元组,字典
  8. python消息队列celery_python异步任务神器celery
  9. 推行ISO9000的作用及相关知识
  10. JAVA动漫论坛BBS系统的设计与实现
  11. Eclipse中SVN上传项目
  12. 谈谈滞后补偿器与PI控制及其原理分析
  13. eclipse配置折叠/展开代码设置
  14. echarts组织架构图
  15. python123测验答案第十周_智慧职教mooc的APPPython程序设计(常州工业职业技术学院)章节测验答案...
  16. python 与 selenium
  17. 国家信息技术服务标准-ITSS
  18. Softmax激活函数
  19. 局域网服务器怎么更改账号,怎么修改访问局域网共享用户名和密码
  20. 学会这道题,解决位运算,布莱恩·克尼根算法!

热门文章

  1. 网络的全双工与半双工
  2. Compose Wheel组件。滚轮组件
  3. python3跑通smpl模型_SMPL模型学习
  4. valist的使用方法(队可变参数的处理)
  5. MFC -- ShowWindow(int nCmdShow)参数总结
  6. PCDATA和CDATA区别
  7. NLP自然语言处理之情感分析分析讲解、知识构建
  8. JAVA多线程-(三)如何让线程暂停
  9. javaweb接口开发
  10. C++中fstream的用法