文章目录
一、概述
Android 事件分发机制是一块比较重要的知识,了解并熟悉整套事件分发机制有助于我们在开发过程中处理各种滑动冲突相关的问题,因此有必要掌握这部分内容。
SDK版本: 29
关联文章:
二、事件分发的基础知识
在分析事件分发流程之前,需要先了解一下相关的基础知识。
- MotionEvent 事件类型。
- 事件分发过程的关联函数。
2.1 MotionEvent 的类型
MotionEvent 类型 | 含义 |
---|---|
ACTION_DOWN | 手指首次落到屏幕上触发(一个事件周期内只会触发一次) |
ACTION_UP | 手指离开屏幕时触发(一个事件周期内只会触发一次) |
ACTION_MOVE | 手指在屏幕上滑动时触发(一个事件周期内会触发很多次) |
ACTION_CANCEL | 事件被上层控件拦截时触发 |
说明: 从 ACTION_DOWN 事件开始到 ACTION_UP 事件结束为一个事件周期。
2.2 事件分发过程的关联函数
事件在 Activity、ViewGroup、View 三者之间进行传递。且按照 Activity --> ViewGroup --> View
方向进行传递。
事件传递的相关方法:
类型 | 方法 | Activity | ViewGroup | View |
---|---|---|---|---|
事件分发 | dispatchTouchEvent | 有 | 有 | 有 |
事件拦截 | onInterceptTouchEvent | 无 | 有 | 无 |
事件消费 | onTouchEvent | 有 | 无 | 有 |
2.3 ViewGroup与View中dispatchTouchEvent方法的区别
ViewGroup.dispatchTouchEvent()
方法内是分发事件的逻辑。
View.dispatchTouchEvent()
方法内是消费事件的逻辑。
2.4 ViewGroup.mFirstTouchTarget 设计成链表?
深入理解事件分发 ViewGroup.mFirstTouchTarget的设计
结论:
- 非多点触控:mFirstTouchTarget链表退化成单个TouchTarget对象。
- 多点触控,目标相同:同样为单个TouchTarget对象,只是pointerIdBits保存了多个pointerId信息。
- 多点触控,目标不同:mFirstTouchTarget成为链表。
为什么要把mFirstTouchTarget设计成链表?
mFirstTouchTarget设计成链表的作用,是用于记录多点触控情况下,多目标控件的派分逻辑。
记录目标的TouchTarget的pointerIdBits又起到什么作用?
pointerIdBits的作用,是配合mFirstTouchTarget,使多点触控时,同个目标可以对多个触控点进行合理的处理逻辑。
三、事件的分发流程
已知事件分发顺序: Activity --> ViewGroup --> View
,所以下面我们也分三部分进行分析。
3.1 Activity 分发流程
// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 调用PhoneWindow.superDispatchTouchEvent()分发进行分发。
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果Window中所有View都不消费该事件,则Activity自己消费,即执行Activity.onTouchEvent()方法。
return onTouchEvent(ev);
}
// PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
// PhoneWindow中持有DecorView的对象mDecor,即视图根节点。
return mDecor.superDispatchTouchEvent(event);
}
// DecorView.java (View的根节点)
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
// Activity.java
public boolean onTouchEvent(MotionEvent event) {
// 类似于点击popWindow之外的区域,popWindow关闭的场景。
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
// Window.java
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
// 事件在边界外,消费事件。
return true;
}
// 事件在边界内,不消费事件。
return false;
}
小结:
- Activity 阶段结束后,事件传递给了 DecorView,即ViewGroup。
- 此流程的传递过程受 ViewGroup.dispatchTouchEvent() 方法返回值的影响。
3.2 ViewGroup 分发流程(分3步)
要点: ViewGroup.dispatchTouchEvent()
方法内是进行事件分发的逻辑,可以分为3部分。
- Step1:请求拦截处理的逻辑。
- Step2:事件接收控件的查找 & DOWN事件的分发,并进行Target赋值。
- Step3:执行事件分发逻辑。
下面先看下这三部分对应的代码位置,代码如下:
// ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 省略代码
// Setp1:请求拦截处理的逻辑
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
intercepted = onInterceptTouchEvent(ev);
// 省略代码
}
// 省略代码
// Step2:当前控件不拦截子控件,则会去查找接收事件的子View。
if (!canceled && !intercepted) {
// 省略代码
// 下面是几个核心方法
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 省略代码
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
}
}
// Step3:执行事件
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 省略代码
dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)
}
// 省略代码
}
// 如果事件被消费,则handled为true
return handled;
}
Step1:事件拦截逻辑
基本流程:
- 在每次接收到 ACTION_DOWN 事件时,会清理上次的 TouchTarget 和
请求不拦截标记(FLAG_DISALLOW_INTERCEPT)
等数据,避免上次的数据影响本次逻辑。 - 执行事件拦击逻辑,具体执行拦截逻辑,分两种场景。
- 通过
getParent().requestDisallowInterceptTouchEvent()
实现子控件请求父控件不拦截事件(优先级高与onInterceptTouchEvent()方法
)。 - 通过
onInterceptTouchEvent()
实现当前控件不拦截子控件事件。
- 通过
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1.每次ACTION_DOWN事件会清楚数据(如Target,FLAG_DISALLOW_INTERCEPT标记等),避免上次数据影响本次的事件消费。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清除TouchTarget(消费事件的View)
cancelAndClearTouchTargets(ev);
// 会重置跟事件相关的有FLAG_DISALLOW_INTERCEPT标记,该标记表示子类请求父类不对事件进行拦截。
resetTouchState();
}
//2. Step1:请求拦截处理的逻辑
// Check for interception.
final boolean intercepted;
// DOWN事件、mFirstTouchTarget不为空(即有子View消费事件)两个场景。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 2.1 子控件通过 getParent().requestDisallowInterceptTouchEvent()来请求父控件不拦截事件。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 2.2 当前ViewGroup可以通过重写onInterceptTouchEvent方法对子控件进行事件拦截,默认不拦截。
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 非ACTION_DOWN事件 & 没有View接收事件
intercepted = true;
}
// 省略代码
}
// 省略代码
return handled;
}
Step2:事件接收控件的查找 & DOWN事件的分发
在分析查找逻辑之前先来看一下关联的几个方法。
ViewGroup.buildTouchDispatchChildList()
:将控件按照Z值从大到小保存在mPreSortedChildren集合中。ViewGroup.getAndVerifyPreorderedIndex()
&ViewGroup.getAndVerifyPreorderedView()
:按照子View在Canvas上绘制的先后顺序从后往前获取View。ViewGroup.isTransformedTouchPointInView()
:判断触摸点是否在子View内部。ViewGroup.dispatchTransformedTouchEvent()
:将事件传递给子View。
ViewGroup.buildTouchDispatchChildList()
方法:
将控件按照Z值从大到小保存在mPreSortedChildren集合中(通常 ViewGroup 中的子 View,Z 值一致)。
可以参考文章: Android控制View绘制顺序的关键方法——setChildrenDrawingOrderEnabled
// ViewGroup.java
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
// 将控件按照Z值大小从大到小保存在mPreSortedChildren集合中。
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
// callers should clear, so clear shouldn't be necessary, but for safety...
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
// 将控件按照Z值从大到小保存在mPreSortedChildren集合中。
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
ViewGroup.getAndVerifyPreorderedIndex()
& ViewGroup.getAndVerifyPreorderedView()
方法:
获取Children[]中指定索引的View返回,这中间包括是否使用自定义的顺序 。
// ViewGroup.java
private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {
// 如果按照自定义顺序,可以在View的getChildDrawingOrder方法中返回指定的drawingPosition。
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("...");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}
protected int getChildDrawingOrder(int childCount, int drawingPosition) {
return drawingPosition;
}
private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
int childIndex) {
final View child;
if (preorderedList != null) {
child = preorderedList.get(childIndex);
if (child == null) {
throw new RuntimeException("...");
}
} else {
child = children[childIndex];
}
return child;
}
ViewGroup.isTransformedTouchPointInView()
方法:
判断当前的触摸点是否在该View的内部(触摸点不再View上,则该View不用处理事件)。
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
// 判断当前的触摸点是否在该View的内部(触摸点不再View上,则该View不用处理事件)。
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
ViewGroup.dispatchTransformedTouchEvent()
方法我们在分析完 ViewGroup.dispatchTouchEvent()
Step2部分的逻辑再分析该方法。
ViewGroup.dispatchTouchEvent()
方法:
基本流程:
- 判断当前事件是否是cancel事件,默认没有事件取消时是不会出现Cancel事件的 (当父控件在MOVE事件中拦截子控件的事件时,会触发子View的CANCEL事件)。
- 执行查找事件接收控件的逻辑。
- 搜索子View过程:将 ChildrenView 按照Z值大小进行排列 (Z值大的排前面,通常ViewGroup中的子View,其Z值一致),如果Z值相同,则按照自定义的顺序进行查找,这部分在
ViewGroup.dispatchDraw()
中也有。 - 查找过程:优先Z值查找,其次按照自定义顺序查找。但是不管是按照Z值还是自定义的顺序,这些最终都与View在Canvas上的绘制的先后顺序有关。在响应触摸事件时,如果最上层控件可以响应事件,我们肯定不会希望包含它的父控件响应。
- 边界校验:找到View后,调用
isTransformedTouchPointInView()
方法判断当前触摸点是否在child控件内,true为触摸点在控件内。 - 事件分发给子View:当触摸点在View的内部时,将事件传递给子View继续分发,方法返回true时代表有控件消费了DOWN事件,因此需要将该View保存在TouchTarget上,代表事件消费的对象。
- 搜索子View过程:将 ChildrenView 按照Z值大小进行排列 (Z值大的排前面,通常ViewGroup中的子View,其Z值一致),如果Z值相同,则按照自定义的顺序进行查找,这部分在
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 省略状态重置代码
final boolean intercepted;
// 省略Step1:请求拦截处理的逻辑
// 1.如果没有事件取消,则canceld为false。
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 2.Step2:当前控件不拦截子控件,则会去查找接收事件的子View。
// canceled因为没有事件的取消,所以为false,而intercepted上面也没有拦截,所以也为false。
if (!canceled && !intercepted) {
// 省略代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
// 省略代码
final int childrenCount = mChildrenCount;
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.
// 2.1 查找可以接收事件的子View。
// 搜索子View过程:将ChildrenView按照Z值大小进行排列(Z值大的排前面)。
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
// 如果preorderedList不存在(即Z都一样大,通常ViewGroup中的子View,其Z值一致),则按照自定义的顺序查找子View。
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 倒序遍历(Children数组的View元素越靠后,越是叶子节点的View)
for (int i = childrenCount - 1; i >= 0; i--) {
// 2.2 查找过程:优先Z值查找,其次按照自定义顺序查找。
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
// 省略代码
// 2.3 isTransformedTouchPointInView方法用于判断当前触摸点是否在child控件内,true为触摸点在控件内。
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 如果触摸点没在child控件内,则继续遍历下一个,直到找到为止(或遍历完)。
continue;
}
newTouchTarget = getTouchTarget(child);
// 省略代码
// 2.4 分发事件给子View,此处传入了接收事件的View(child),返回true表示有子View消费事件。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 省略代码
// 2.5 将消费事件的View包装成TouchTarget。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 此处表示有新的Target接收了事件。
alreadyDispatchedToNewTouchTarget = true;
// 这里找到第一个View后就会退出查找逻辑。
break;
}
// 省略代码
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 省略Step3:执行事件逻辑
}
// 省略代码
// 如果事件被消费,则handled为true
return handled;
}
ViewGroup.dispatchTransformedTouchEvent()
方法:
基本流程:
- 当前控件向子类分发事件时,不管是
ACTION_CANCEL
事件还是其它事件,都会根据child==null是成立
分两种情况。 - 如果
child == null
,即没有找到接收该事件的子View,则调用ViewGroup父类的dispatchTouchEven()t方法,即View.dispatchTouchEvent()
,该方法主要是消费事件的逻辑(不一定消费)。 - 如果
child != null
,即找到接收该事件的子View,则调用子View的dispatchTouchEven()方法,如果子View是一个ViewGroup,则继续执行ViewGroup.dispatchTouchEven()
的事件分发逻辑,如果子View是一个View,则执行View.dispatchTouchEvent()
逻辑。
说明: 接收事件不代表消费事件,仅代表有该事件往下进行传递。
// ViewGroup.java
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();
// 接收ACTION_CANCEL事件
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
// cancel=true时,强制传递ACTION_CANCEL事件给子View。
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
// 没有子类接收事件,则自己尝试处理。
handled = super.dispatchTouchEvent(event);
} else {
// 有子类接收事件,则将事件继续分发给子类。
handled = child.dispatchTouchEvent(event);
}
// cancel=true时,,还原真实的MotionEvent事件类型。
event.setAction(oldAction);
return handled;
}
// 省略代码
// 处理其它非ACTION_CANCEL事件。
final MotionEvent transformedEvent;
// Perform any necessary transformations and dispatch.
if (child == null) {
// 没有子类接收事件,则自己尝试处理。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// 有子类接收事件,则将事件继续分发给子类。
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
ViewGroup.addTouchTarget()
方法:
将接收事件的子View包装成 TouchTarget 对象。
// ViewGroup.java
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
Step3:事件分发逻辑
基本流程:
1.当 mFirstTouchTarget == null
时 (即没有找到子View接收事件,或者事件被当前View拦截),且传入child=null时,表示当前View尝试自己处理事件,即最终调用 View.dispatchTouchEvent()
方法。
2. 当 mFirstTouchTarget != null
时,如果是 DOWN 事件,则在Step2过程中(查找到接收事件的子View)就已经分发过事件了,且设置 alreadyDispatchedToNewTouchTarget=true
,因此这里直接对handled进行赋值(handled=true
)。
3. 如果是 MOVE/UP
事件,则会在Step3中执行(在Step2中不处理),此时需要根据cancelChild 状态值来判断向子View中执行ACTION_CANCEL
事件还是 MOVE/UP
事件。
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 省略状态重置代码
final boolean intercepted;
// 省略Step1:请求拦截处理的逻辑
TouchTarget newTouchTarget = null;
// 省略Step2:当前控件不拦截子控件,则会去查找接收事件的子View。
if (!canceled && !intercepted) {
// 省略代码
}
// Step3:执行事件分发逻辑
// Dispatch to touch targets.
// 如果没有找到子View接收事件,或者事件被拦截了,则mFirstTouchTarget==null成立。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 1.分发事件时child传入null,代表尝试自己处理事件。
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;
// target != nul,即有子View接收事件。
while (target != null) {
final TouchTarget next = target.next;
// 2.alreadyDispatchedToNewTouchTarget==true说明DOWN事件已经分发过一次,且找到了接收事件的View(逻辑在Step2:查找接收事件的子View)。此时mFirstTouchTarget肯定不为null(上面已经判断过了),即target!=null。
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 将handled赋值为true,并最终回传给Activity.dispatchTouchEvent()。
handled = true;
} else {
// 3.下面主要处理MOVE和UP事件。
// resetCancelNextUpFlag 方法返回当前View是否需要取消事件,并重置状态为false。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 分两种情况:
// Condition1:拦截MOVE事件或target.childView本身需要执行ACTION_CANCEL事件,此时cancelChild=true且target.child != null,则向子View分发ACTION_CANCEL事件。
// Condition2:没有拦截MOVE事件且target.child本身不需要执行ACTION_CANCEL事件,则向子View分发当前的ev事件。
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 回收包装子View的TouchTarget链表。
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 省略代码
}
// 省略代码
// 如果事件被消费,则handled为true
return handled;
}
小结:
3.3 View 分发流程
View.dispatchTouchEvent()
方法:
// View.java
public boolean dispatchTouchEvent(MotionEvent event) {
// 代码省略
boolean result = false;
// 代码省略
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 1.这里执行onTouch事件,所以Touch事件是最先被执行的。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 2.如果onTouch返回false,则执行View.onTouchEvent()方法(点击事件逻辑都在里面)。
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 代码省略
return result;
}
View.onTouchEvent()
方法:
// View.java
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 控件可点击状态
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// 代码省略
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 代码省略
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
// 长按在DOWn事件中触发,因此在UP事件响应点击事件时要移除长按逻辑,避免点击和长按逻辑都被触发。
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// UP事件中触发点击事件。
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
// 代码省略
}
break;
case MotionEvent.ACTION_DOWN:
// 代码省略
if (isInScrollingContainer) {
// 代码省略
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
// 在DOWN事件中触发长按点击事件。
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_MOVE:
// 代码省略
// MOVE事件中也会触发长按事件(长按时手指不可避免会出现移动)。
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
break;
}
// 响应可点击的逻辑时,都会返回true。
return true;
}
return false;
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 最终触发外部设置的点击事件。
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
小结: