Android Touch事件分发处理机制详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Touch事件分发处理机制详解相关的知识,希望对你有一定的参考价值。
参考技术Aandroid应用的开发过程不可能不涉及到Touch事件的处理,简单地如设置OnClickListener、OnLongClickListener等监听器处理View的点击事件,复杂地如在自定义View中通过重写onTouchEvent来捕获用户交互事件以定制出各种效果,在使用的过程中或多或少会遇到一些奇怪的Bug,让你对Touch事件“从哪来,到哪去”产生迷之疑惑,经过多少次徘徊之后终于决定系统的分析下源码,本文就给大家分享下我的收获。
MotionEvent作为Touch事件的载体,采用时间片来管理Touch事件所有相关行为的数据,本文这样理解时间片这个概念:
通常MotionEvent会将触发当前事件的Pointer作为主要Pointer,其PointerIndex为0,而MotionEvent通过提供getX()这类不带index参数的接口以更方便的操作主要Pointer的数据。
了解了MotionEvent的组成结构之后,接下来就可以分析MotionEvent包含的事件类型了,MotionEvent通过getAction接口来获取事件Action,而Action中低8位地址存储的是事件类型(对于触摸事件来说,主要包括Down、Move、Up、Cancel、PointerDown、PointerUp),高8位地址存储的是PointerId(当事件类型为PointerDown、PointerUp时)。通常来说事件会以Down开始,以Up或Cancel结束,各事件所承担的角色以及各自的特点在分析事件分发与处理的过程时再详细说明。
另外,MotionEvent中的Flag需要说明一下:
本文仅分析Touch事件在Framework中Java层的传递,因此从事件传递到Activity开始分析。当Touch事件传递给Activity时,会调用Activity.dispatchTouchEvent(MotionEvent),Activity会将事件传递给其Window进行处理,实际会调用PhoneWindow.superDispatchTouchEvent(MotionEvent),PhoneWindow会将该事件传递给Android中View层级中的顶层View(即DecorView)进行处理:
在Window未设置Callback的情况下,会调用父类的dispatchTouchEvent,DecorView继承自FrameLayout,然后FrameLayout并未实现dispatchEvent,因此最终调用ViewGroup.dispatchTouchEvent,也就是Touch事件分发的核心逻辑所在,前文中提到MotionEvent中事件类型主要包括Down、Move、Up、Cancel、PointerDown、PointerUp,而dispatchTouchEvent根据事件的不同类型会做不同处理,因此这里分别进行分析:
Down事件处理
非异常情况下,Touch事件的事件周期总是以Down事件开始的,因此Down事件在整个事件分发逻辑中起关键作用,将决定了后续Move、Up及Cancel事件的处理主体,先看一张Down事件分发的流程图:
从流程图中可以看到,Down事件的分发逻辑主要目的在于寻找到能处理该Touch事件的View控件(该View为以当前ViewGroup为Root节点的View层级中的View,利用寻找到的View创建事件处理Target),整个处理逻辑主要包含以下几步:
Move、Up、Cancel事件处理
完成Down事件的分发逻辑后,就确定了该Down事件后续Move、Up及Cancel事件的处理主体(注意:这里并没有确定PointerDown事件的处理主体,关于PointerDown事件的分发逻辑稍后分析),先通过一张流程图来感受下Move、Up、Cancel事件的分发逻辑:
从流程图可以看出,对于Move、Up、Cancel事件的分发步骤如下:
PointerDown事件处理
PointerDown事件是在支持多Pointer(调用setMotionEventSplittingEnabled将FLAG_SPLIT_MOTION_EVENTS置位)的环境下,当有新的Pointer按下时产生的,该事件处理的特殊性在于会重新遍历View层级,寻找可以处理新Pointer事件的Target,具体流程参考Down事件的分发逻辑;遍历结束若仍没有找到处理该事件的Target,则会将新Pointer的处理权设置给已有Target中最早被添加的Target。完成Target的寻找之后,会将该事件通过dispatchTransformedTouchEvent传递至所有已有Target进行处理,可以通过下面流程图,对PointerDown事件的处理有一个更全局的认识:
PointerUp事件处理
相对于Up事件来说,对于PointerUp事件的处理区别在于当传递至所有已有Target结束之后并不能标记以Down事件起始的整个事件周期结束,仅能标记其关联Pointer(以PointerDown事件起始)的事件周期结束,因此不会清除所有状态,而仅会从已有Target中移除掉与该Pointer相关的部分。
onInterceptTouchEvent
在ViewGroup进行事件分发的过程中,会调用该函数来确定是否需要拦截事件,当该函数返回true时该事件将会被拦截,即不会进行正常的View层级传递,而是直接由该ViewGroup来处理,而拦截后的操作需要根据拦截事件的类型不同而不同:
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)
在将事件传递给Target进行处理之前会调用该函数对MotionEvent进行处理:
MotionEvent.split(int idBits)
判断一个View控件是否消费一个事件,是由View.dispatchEvent的返回值来决定的,而View.dispatchEvent用于寻找事件的最终消费者,话不多说,还是通过一张流程图来个直观感受:
从流程图中可以看出,View会根据ouch事件对Scroll状态进行调整,并寻找该事件的最终处理器:
View.dispatchEvent将向其直接ViewGroup返回是否消费掉该事件,返回值将决定上级ViewGroup是否需要继续询问其他子View是否需要消费该事件。这就是View中分发事件的逻辑,真是简单粗暴!
从View.dispatchEvent的分析中可以发现当未对View设置mTouchListener或mTouchListener未消费掉该事件时,Touch事件最终将由View.onTouchEvent来决定是否消费,自定义View可以重写该方法实现自身的逻辑,此处仅分析View中的通用处理逻辑:
从上述分析可以很开心地发现熟悉的onClick及onLongClick事件的产生逻辑,若是之前没看过类似的文章,应该会有原来如此的感觉吧,哈哈~~
至此,Touch事件的分发与处理流程算是走通了,个人看完整个源码之后有种豁然开朗的感觉,能很清晰的分析向“为什么事件有时候传到某个View有时候却不传?”、“有时候只传前面几个事件后面却不传了?”等问题,也希望本文的分析能让你更清晰地感知Android中Touch事件的传递流程,如果发现文中有何错误,希望不吝赐教!
Android 进阶——Framework 核心之Touch事件分发机制详细攻略
文章大纲
引言
Android 事件详细总结。
该文章基于android-28,仅分析Framework java层代码逻辑,仅供参考,不敢保证百分百正确无误。
一、Android 事件分发概述
一般说来当用户触摸屏幕或者按键操作,首次触发的是底层硬件驱动,驱动收到事件后并将该相应事件写入到输入设备节点, 便产生最原生态的内核事件。接着,输入(Input)系统取出原生态的事件再经过层层封装后成为KeyEvent或者MotionEvent ,最后交付给相应的目标窗口(Window)来消费该输入事件。其中Input模块的主要组成有:
- Native层的InputReader负责从EventHub取出事件并处理,再交给InputDispatcher;
- Native层的InputDispatcher接收来自InputReader的输入事件,并记录WMS(WindowManagerService)的窗口信息,用于派发事件到合适的窗口;
- Java层的InputManagerService跟WMS交互,WMS记录所有窗口信息,并同步更新到IMS,为InputDispatcher正确派发事件到ViewRootImpl提供保障。
简而言之,Android 中事件传递按照从上到下进行层级传递,事件处理从 Activity 开始到 ViewGroup 再到 View。事件传递方法包括dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent,其中前两个是 View 和 ViewGroup 都有的,最后一个是只有 ViewGroup 才有的方法。其中onTouch 方法要先于 onClick 触发执行,onTouch 在 dispatchTouchEvent 中被调用,而 onClick 在 onTouchEvent 方法中被调用,因此onTouchEvent 要后于 dispatchTouchEvent 方法的调用。
二、Touch事件分发的对象MotionEvent
MotionEvent 继承自InputEvent并实现Parcelable接口,作为事件分发机制的核心对象,在Android中当用户触摸屏幕上的View 或 ViewGroup(及其子View),将产生点击事件(Touch事件)并根据Touch事件的发生时触摸的位置、时间、类型等相关细节封装不同类型的MotionEvent对象进行传递,该对象用于记录所有与移动相关的事件信息(事件分发和处理由Activity去调用native层完成),比较典型的有以上四种:
事件ACTION | 触发场景 | 单次事件流中触发的次数 |
---|---|---|
MotionEvent.ACTION_DOWN | 在屏幕被按下时(所有事件的源头) | 1次 |
MotionEvent.ACTION_MOVE | 在屏幕上进行滑动时 | 0次或者多次 |
MotionEvent.ACTION_UP | 从屏幕上抬起时 | 0次或者1次 |
MotionEvent.ACTION_CANCEL | 结束事件(非人为原因) | 0或者1次 |
所以一次完整的MotionEvent事件,是从用户触摸屏幕到离开屏幕。整个过程的动作序列:ACTION_DOWN(1次) —> ACTION_MOVE(N次) -> ACTION_UP(1次)。
而对于多点触摸,每一个触摸点Pointer会有一个id和index。对于多指操作,通过pointerindex来获取指定Pointer的触屏位置。比如单点操作时通过getX()获取x坐标,而多点操作时通过getX(pointerindex)获取x坐标。
MotionEvent 还有很多的ACTION类型,MotionEvent需要继承native 层InputEvent(其实是内核层的Input机制),因为Android Framework 通过JNI 进行处理,同时需要跨进程因而MotionEvent实现了Parcelable序列化接口。
三、分发事件的核心对象及方法
1、分发事件的核心对象
-
Activity——作为Android四大基本组件之一,当手指触摸到屏幕时,屏幕硬件逐行不断地扫描每个像素点,获取到触摸事件后,从底层产生中断上报。再通过native层调用Java层InputEventReceiver中的dispatchInputEvent方法。最后经过层层调用交由Activity的dispatchTouchEvent方法来处理。
-
View——作为所有视图对象的父类,实现了Drawable.Callback(动画相关的接口)、KeyEvent.Callback(按键相关的接口)和AccessibilityEventSource(交互相关的接口)。
-
ViewGroup——ViewGroup是一个继承了View并实现了ViewParent(用于与父视图交互的接口), ViewManager(用于添加、删除、更新子视图到Activity的接口)的抽象类,作为盛放其他View的容器,可以包含View和ViewGroup,是所有布局的父类或间接父类。
2、事件分发的核心方法
Touch事件都源自按下屏幕里Activity中的View或者ViewGroup(及其子View),所以事件的处理都是由Activity、View或ViewGroup对象完成的,换言之,只在Activity、View或ViewGroup里拥有处理事件系列方法如下表所示:
从源码中可以得知在Activity、 ViewGroup 和View中都存在 dispatchTouchEvent 和 onTouchEvent 方法(但是在 ViewGroup 中还有一个 onInterceptTouchEvent 方法),他们都接受了一个MotionEvent类型的参数用于标记各种动作事件且返回值都是boolean型,true则代表不往下传递,false则表示继续往下传递,那么这些方法有何功能的呢?
-
boolean dispatchTouchEvent(MotionEvent event)——负责Touch事件的分发,Android 中所有的事件都必须经过此方法的分发,然后决定是自身消费当前事件还是继续往下分发给子View返回 true 则表示不继续分发,反之返回 false 则继续往下分发;而如果ViewGroup 则是先分发给 onInterceptTouchEvent 进行判断是否拦截该事件。
-
boolean onTouchEvent(MotionEvent event)—— 负责Touch事件的处理,返回 true 则表示消费当前事件,反之返回 false 则不处理交给子View继续进行分发。
-
boolean onInterceptTouchEvent(MotionEvent event)—— 负责Touch事件的拦截 返回 true 则表示拦截当前事件就不继续往下分发,交给自身的 onTouchEvent 进行处理;而返回 false 则不拦截,继续往下传。这是 ViewGroup 特有的方法,因为 ViewGroup 作为容器可以存放其他子 View,而View 则不能,换言之,只有 ViewGroup才有拦截事件的能力。
事件分发的核心流程都是围绕这些方法进行的,由不同的对象直接或者间接调用进行处理。
四、Touch事件分发的流程
事件分发有多种类型, 以下是Touch相关的事件分发大致流程:
Activity——>Window(PhoneWindow)——>DecorView——>ViewGroup——>View。
1、Activity的事件处理流程
事件的分发是由Activity开始的,当用户点击屏幕的时候首先接触到的就是Activity,从而触发Activity#dispatchTouchEvent方法,
/**
* android.app.Activity#dispatchTouchEvent
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
* @param ev The touch screen event.
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev)
//如果是DOWN类型则说明是一个全新的事件
if (ev.getAction() == MotionEvent.ACTION_DOWN)
//默认为空实现,可以被重写,如果你想要知道用户已经与Activity进行交互
onUserInteraction();
//调用Window的dispatchTouchEvent方法,并把事件分发至PhoneWindow(获取当前Activity的顶层窗口是PhoneWindow),返回false表示当前事件没有被消费。
if (getWindow().superDispatchTouchEvent(ev))
return true;
//如果PhoneWindow消费了事件则把事件分发至Activity的onTouchEvent
return onTouchEvent(ev);
Activity#dispatchTouchEvent返回结果之前,会先分发到ViewGroup&View,而最终的执行分为两个分支(抛开中间细节):
- getWindow().superDispatchTouchEvent(ev)返回flase时,代表当前事件没有被Activity中的任何View或者ViewGroup处理(即DecorView #dispatchTouchEvent方法返回false),则此次事件最终是由Activity#onTouchEvent 去处理的。
/**
* android.app.Activity#onTouchEvent
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
* @param event The touch screen event being processed.
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event)
//判断是否屏幕出界,比如说点击到屏幕的非有效区域时,当窗口需要关闭时,消费掉当前event
if (mWindow.shouldCloseOnTouch(this, event))
finish();
return true;
return false;
- getWindow().superDispatchTouchEvent(ev)返回true时,代表事件已被Activity中的View或者ViewGroup消费了。
再回到分发流程,Activity#dispatchTouchEvent执行后,接着会通过getWindow()方法得到当前Activity的顶层窗口(即PhoneWindow)并调用它的superDispatchTouchEvent方法把事件分发到PhoneWindow,执行PhoneWindow#superDispatchTouchEvent方法传入到DecorView(根ViewGroup)…
DecorView继承自 FrameLayout 并实现了RootViewSurfaceTaker和WindowCallbacks接口,是当前界面的最外(顶)层容器,即setContentView方法所设置的View的父容器根ViewGroup。
//com.android.internal.policy.PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event)
//分发至DecorView
return mDecor.superDispatchTouchEvent(event);
分发到DecorView,再通过DecorView#superDispatchTouchEvent方法
//com.android.internal.policy.DecorView#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event)
return super.dispatchTouchEvent(event);//实际上调用的是ViewGroup#dispatchTouchEvent
接下来分发到了ViewGroup中,ViewGroup的分发流程比较复杂,篇幅有限会省略部分逻辑。
2、ViewGroup&View的事件分发流程
ViewGroup&View的事件分发流程的起点是从PhoneWindow#superDispatchTouchEvent开始的,首先分发至根ViewGroup(DecorView)并执行ViewGroup#dispatchTouchEvent方法。
//android.view.ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
//辅助功能跨进程调用,是当前应用与系统层进行交互的,模拟的点击事件
if (mInputEventConsistencyVerifier != null)
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
...
//是否消费了当前事件的标识,最终的返回值
boolean handled = false;
//根据隐私策略而来决定是否过滤本次触摸事件,相当于是检测当前事件,功能类似检测操作UI时是否在主线程中
if (onFilterTouchEventForSecurity(ev))
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN)
// 发生ACTION_DOWN事件, 则取消并清除之前所有的触摸targets,可能是由于App切换、ANR或者其他原因抛弃了上一次的UP或者CANCEL,需要取消并移除所有目标
cancelAndClearTouchTargets(ev);
resetTouchState(); // 重置触摸状态为下一次循环做准备
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN;才进入此区域,主要功能是拦截器
//第一次触摸屏幕时是DOWN,只有发生过ACTION_DOWN事件,则mFirstTouchTarget != null;
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)
//可以通过在其子View中去调用父ViewGroup的requestDisallowInterceptTouchEvent方法设置是否允许拦截,让父View是否拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断是否允许调用拦截器
if (!disallowIntercept)
//!!调用拦截逻辑的方法!!
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
else
intercepted = false;
else
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted = true;
...
//不取消事件,同时不拦截事件, 并且是Down事件时
if (!canceled && !intercepted)
//把事件分发给所有的子视图,寻找可以获取焦点的视图。
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)
final int actionIndex = ev.getActionIndex(); // down事件等于0
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign); //清空早先的触摸对象
final int childrenCount = mChildrenCount;
//第一次down事件,同时子视图不会空时
if (newTouchTarget == null && childrenCount != 0)
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event. Scan children from front to back.
//创建可以接收事件的子View,并按照z轴值对子View进行排序,因为子View可能存在相互重叠的部分,从顶层到底层排序
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//当前ViewGroup 中所有的子View数组
final View[] children = mChildren;
/* 倒序遍历,从底层到顶层,从最底层的父视图开始遍历, ** 找寻newTouchTarget,并赋予view与 pointerIdBits; ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
for (int i = childrenCount - 1; i >= 0; i--)
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//获取当前ViewGroup中的子View
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 如果当前视图无法获取用户焦点,则跳过本次循环
if (childWithAccessibilityFocus != null)
if (childWithAccessibilityFocus != child)
continue;
childWithAccessibilityFocus = null;
i = childrenCount - 1;
//如果view不可见或正在执行动画,或者触摸的坐标点不在view的范围内,则跳过本次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null))
ev.setTargetAccessibilityFocus(false);
continue;
//获取当前的View的包装对象
newTouchTarget = getTouchTarget(child);
// 已经开始接收触摸事件,并退出整个循环。
if (newTouchTarget != null)
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
//重置取消或抬起标志位
resetCancelNextUpFlag(child);
//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup,开始事件分发,child为null时传递到View#dispatchTouchEvent,而child不为null时则调用child自身的dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) // [见小节2.4.4]
// 获取TouchDown的时间点
mLastTouchDownTime = ev.getDownTime();
// 获取TouchDown的Index
if (preorderedList != null)
for (int j = 0; j < childrenCount; j++)
if (children[childIndex] == mChildren[j])
mLastTouchDownIndex = j;
break;
else
mLastTouchDownIndex = childIndex;
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null,给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
ev.setTargetAccessibilityFocus(false);
// 清除视图列表
if (preorderedList != null) preorderedList.clear();
if (newTouchTarget == null && mFirstTouchTarget != null)
//将mFirstTouchTarget的链表最后的touchTarget赋给newTouchTarget
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null)
newTouchTarget = newTouchTarget.next;
newTouchTarget.pointerIdBits |= idBitsToAssign;
///#endif if (!canceled && !intercepted)
//ViewGroup进行事件拦截之后直接越过前面的逻辑,执行到这里,mFirstTouchTarget赋值是在通过addTouchTarget方法获取的;
// 只有处理ACTION_DOWN事件,才会进入addTouchTarget方法。这也正是当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件的原
if (mFirstTouchTarget == null)
//没有触摸target,则由当前ViewGroup来处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
else
//如果View消费ACTION_DOWN事件,那么MOVE,UP等事件相继开始执行
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;
if (cancelChild)
if (predecessor == null)
mFirstTouchTarget = next;
else
predecessor.next = next;
target.recycle();
target = next;
continue;
predecessor = target;
target = next;
//当发生抬起或取消事件,更新触摸targets
if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)
resetTouchState();
else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP)
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
//此处大括号,是if (onFilterTouchEventForSecurity(ev))的结尾
//通知verifier由于当前时间未处理,那么该事件其余的都将被忽略
if (!handled && mInputEventConsistencyVerifier != null)
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
return handled;
在ViewGroup每次调用ViewGroup#dispatchTouchEvent方法时,当requestDisallowInterceptTouchEvent方法返回true时则说明允许事件拦截,就会先执行ViewGroup#onInterceptTouchEvent判断是否进行事件拦截,
/**
* Implement this method to intercept all touch screen motion events.
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
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;
返回true则进行拦截,就会越过 if (!canceled && !intercepted)分支,直接执行ViewGroup#dispatchTransformedTouchEvent方法
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
* ViewGroup 真正分发事件的逻辑
* @param child null则调用View#dispatchTouchEvent;反之则调用child里的dispatchTouchEvent方法
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL)
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null)
handled = super.dispatchTouchEvent(event);
else
handled = child.dispatchTouchEvent(event);
event.setAction(oldAction);
return handled;
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerId以上是关于Android Touch事件分发处理机制详解的主要内容,如果未能解决你的问题,请参考以下文章
Android 进阶——Framework 核心之Touch事件分发机制详细攻略
Android 进阶——Framework 核心之Touch事件分发机制详细攻略