Android View 的事件体系 -- 事件分发机制
文章是阅读了《Android 开发艺术探索》这本书较详细的理解了事件分发机制后,再通过研究源代码,系统性的总结事件分发机制,主要目的是为了更好的理解对同一系列事件(DOWN --> MOVE --> UP),它的分发过程是怎么样的,所以文章的讲解要比书上的细,要更深入些(^_^)。
文中源代码使用的是 API 26,因为新版本的 Studio 不能打开 API 19 的源码了,所以就直接用 API 26 了(^_^)。
文中配有流程图,结合流程图,大家可以更好的理解,和记忆(对我而言后者更重要)(^_^)。另外本文对源码的解析颗粒度会很小,所以流程图并不会把所有源码包含进来。
本来是想直接写在《Android View 的事件体系》这篇博客中,但发现这部分内容太多了,就独立出来了。如果对 Android 事件完全没有接触的话,可以先看下事件体系博客(只要看到事件分发机制部分即可)。
前言
所谓的点击事件分发,其实就是对 MotionEvent 事件的分发过程,即一个 MotionEvent 产生以后,系统需要把这个事件传递给具体的View,而这个传递过程就是分发过程。
目录(兼容稀土掘金)
- 一、三个重要方法
- 二、事件分发机制简略过程图
- 三、从 Activity 到 View 的事件传递
- 四、View 的事件传递
- 流程图
- dispatchTouchEvent:全部源码
- onTouchEvent:整体源码(简述)
- onTouchEvent:clickable 判定
- onTouchEvent:View 不可用
- onTouchEvent:存在代理
- onTouchEvent:点击事件
- performClick:全部源码
- 五、ViewGroup 的事件传递
- 流程图
- onInterceptTouchEvent:决定 ViewGroup 是否拦截事件。
- onTouchEvent:ViewGroup 自身尝试消费事件。
- dispatchTouchEvent:整体源码(简述)
- dispatchTouchEvent:拦截判定模块(核心)
- dispatchTouchEvent:ACTION_DOWN 重置
- dispatchTouchEvent:ViewGroup 自身消费事件(核心)
- dispatchTouchEvent:子 View 消费事件(核心)
- dispatchTouchEvent:遍历所有子 View(简述)
- dispatchTouchEvent:子 View 不能接受事件判定
- dispatchTouchEvent:寻找方式一:直接从链表中找到可消费该事件的子 View
- dispatchTouchEvent:寻找方式二:事件被子 View 消费(DOWN 事件)
- 六、滑动冲突
- requestDisallowInterceptTouchEvent:修改 mGroupFlags
一、三个重要方法
// Activity
boolean dispatchTouchEvent(MotionEvent event) // 事件分发
boolean onTouchEvent(MotionEvent event) // 事件处理
// View:
boolean dispatchTouchEvent(MotionEvent event) // 事件分发
boolean onTouchEvent(MotionEvent event) // 事件处理
// ViewGoup:
boolean onInterceptTouchEvent(MotionEvent event) // 事件拦截
boolean dispatchTouchEvent(MotionEvent event) // 事件分发
boolean onTouchEvent(MotionEvent event) // 事件处理
复制代码
返回值说明:
- true: 事件被拦截,事件处理到此为止,不再继续分发
- false: 事件未被拦截,继续往下/上一个控件传递
二、事件分发机制简略过程图
通过上图,可得:
- View:OnTouchListener --> onTouchEvent --> OnClickListener
- View 是可点击的,则事件就会被该 View 消费
三、从 Activity 到 View 的事件传递
/**
* Activity 类
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
/**
* Window 类
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
/**
* PhoneWindow extends Window 类
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
/**
* DecorView 类
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
复制代码
四、View 的事件传递
从下面的流程图可知:
- OnTouchListener --> onTouchEvent --> OnClickListener
- View 可点击,无论 View 是否可用,都会消费事件
dispatchTouchEvent:全部源码
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;
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;
}
复制代码
onTouchEvent:整体源码(简述)
从源代码可知:View 可点击,无论 View 是否可用,都会消费事件
public boolean onTouchEvent(MotionEvent event) {
...
// clickable 判定
final boolean clickable = ...
// View 不可用处理
if ((viewFlags & ENABLED_MASK) == DISABLED) {
...
return clickable;
}
// 存在代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// View 可点击
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!clickable) {
...
break;
}
...
// 点击事件处理
if(...)
performClick();
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
复制代码
onTouchEvent:clickable 判定
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
复制代码
通过 setOnClickListener、setOnLongClickListener、setOnContextClickListener 等方法设置 View 的点击事件,都会使 View 的 clickable 属性等于 true
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
复制代码
public void setContextClickable(boolean contextClickable) {
setFlags(contextClickable ? CONTEXT_CLICKABLE : 0, CONTEXT_CLICKABLE);
}
复制代码
void setFlags(int flags, int mask) {
...
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
...
}
复制代码
onTouchEvent:View 不可用
从源代码可知: 一旦 View 不可用,流程就会结束,返回值为 clickable
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
复制代码
onTouchEvent:存在代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
复制代码
onTouchEvent:点击事件
从源代码可知:
- MotionEvent.ACTION_UP 事件触发 performClick() 方法的执行
- View 只要可点击,那么当前 View 就会消费掉这个事件
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!clickable) {
...
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
...
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
复制代码
performClick:全部源码
如下,调用了 onClick 方法
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
复制代码
五、ViewGroup 的事件传递
必要说明:
- 一般情况下,ViewGroup 会遍历所有子 View,尝试让某个子 View 消费事件。仅当所有子 View 都不消费该事件时,ViewGroup 自身才会尝试消费该事件
- 拦截指 ViewGroup 不再让事件遍历经过子 View,直接由自身尝试消费该事件
图片待补充:
onInterceptTouchEvent:决定 ViewGroup 是否拦截事件。
由源代码可知:默认情况下 ViewGroup 不拦截事件(除特殊情况)
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;
}
复制代码
onTouchEvent:ViewGroup 自身尝试消费事件。
从源码可知,ViewGroup 并没有重写该方法。
dispatchTouchEvent:整体源码(简述)
先让大家对整体源码有个划分
public boolean dispatchTouchEvent(MotionEvent ev) {
// ACTION_DOWN 重置标志(代码见下)
// 拦截判定模块(代码见下)
// ACTION_DOWN 事件时,遍历所有子 View,寻找能消费该事件的子 View(代码见下)
// ViewGroup 自身消费事件,或子 View 消费事件(代码见下)
}
复制代码
dispatchTouchEvent:拦截判定模块(核心)
注:说明很长,请先看下源代码,在对照说明一起看。
必要说明(以下说明具体原因见之后的源代码):
- 1)boolean intercepted = true 表示当前事件被 ViewGroup 拦截,不再传递给子 View,交由 ViewGroup 自身处理该事件。
- 2)当 ViewGroup 的某个子 View 消费了事件后,mFirstTouchTarget 就指向该子 View。否则为 null
- 3)ACTION_DOWN 事件会先对 mGroupFlags、mFirstTouchTarget 等进行重置,再执行后续逻辑
由上述可知,对同一系列事件(DOWN、MOVE、UP):
- 4)mFirstTouchTarget != null,表示上一个事件由某个子 View 消费
- 5)mFirstTouchTarget == null,表示上一个事件由 ViewGroup 自身处理,出现有场景:
- ViewGroup 没有子 View
- ViewGroup 所有子 View 都不消费事件
- ViewGroup 的 onInterceptTouchEvent 方法一直返回 true
以下源代码可知:
- 6)当事件不是 ACTION_DOWN,且 mFirstTouchTarget == null 时,表示 ViewGroup 继续由自身处理事件(关联第2点、第5点)
- 7)boolean disallowIntercept = false,表示 ViewGroup 可能会拦截事件。反之则表示 ViewGroup 不会拦截事件
- 8)事件为 ACTION_DOWN 时:
- onInterceptTouchEvent 必然被执行(关联第3点)
- 该事件要么被某个可点击的子 View 消费,要么 ViewGroup 自身消费(关联 View 的事件传递)
- 一旦该事件被某个可点击的子 View 消费,则 mFirstTouchTarget != null,反之 mFirstTouchTarget == null(关联第2点)
- 9)链表 mFirstTouchTarget != null 时:
- mGroupFlags 不发生改变的情况下,当前事件将继续被链表中的某个子 View 消费(假设该子 View 会继续消费掉该事件)
-
- mGroupFlags 可以通过 requestDisallowInterceptTouchEvent(boolean) 进行设置
- 一旦 mGroupFlags 被设置,则 disallowIntercept = true,那么 ViewGroup 将不会拦截除 ACTION_DOWN 以外的事件(关联第3点)
从上述可知:
- 11)ACTION_DOWN 事件被 ViewGroup 自身消费,则后续的一系列事件,仍然由 ViewGroup 自身消费
- 12)ACTION_DOWN 事件被某个子 View 消费,则后续的一系列事件,仍然该子 View 消费(假设该子 View 会消费掉该事件)
上述证明见:
-
ACTION_DOWN 重置:第3点
-
ViewGroup 自身消费事件:第1点、第6点
-
事件被子 View 消费(DOWN 事件):第2点
-
本章节:第7点
-
ACTION_DOWN 重置、ViewGroup 自身消费事件、事件被子 View 消费(DOWN 事件)组合证明:第8点
-
子 View 消费事件、遍历所有子 View(简略)组合证明:第9点
-
滑动冲突:第10点
// 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);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
复制代码
dispatchTouchEvent:ACTION_DOWN 重置
该段代码在拦截判断之前,可以看出以下信息被重置了:
- mGroupFlags
- mFirstTouchTarget
该部分源码证明上述拦截判定模块的
- 第3点
- 第8点部分证明
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// 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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
复制代码
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
复制代码
dispatchTouchEvent:ViewGroup 自身消费事件(核心)
很明显 mFirstTouchTarget == null 的时候,ViewGroup 自身尝试消费事件,讲解:
- dispatchTransformedTouchEvent:实际就是调用子 View 的 dispatchTouchEvent 方法
- 很明显 ViewGroup 自身消费事件时,child == null,意味着 ViewGroup 会走一遍 ViewGroup 作为 View 层次的事件传递过程
该部分源码证明上述拦截判定模块的
- 第1点
- 第6点
- 第8点部分证明
// ACTION_DOWN 重置(mFirstTouchTarget = null)
// 拦截判定
boolean intercepted = ...
if (!canceled && !intercepted) {
...
}
// 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 {
// 子 View 消费 ACTION_DOWN 之外的事件
...
}
复制代码
/**
* 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.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
...
handled = child.dispatchTouchEvent(event);
...
}
return handled;
}
复制代码
dispatchTouchEvent:子 View 消费事件(核心)
必要说明(以下说明具体原因见之后的源代码):
- 13)DOWN 事件被某个子 View 消费,则 alreadyDispatchedToNewTouchTarget == true
- 14)DOWN 事件被某个子 View 消费,则 mFirstTouchTarget != null,且 mFirstTouchTarget 包含该子 View。
- 15)对于同一系列事件(DOWN、MOVE、UP)只有 ACTION_DOWN 才能遍历所有子 View,其他事件不会遍历
结合上述以及由下述源码可知:
- 当前事件为 ACTION_DOWN,且被消费时,事件不再重复分发。
- 事件不为 ACTION_DOWN 时,不会遍历所有子 View,而是直接通过 mFirstTouchTarget 链表遍历查找消费过 ACTION_DOWN 事件的子 View,让这些子 View 尝试消费事件,注意【target.pointerIdBits】属性,该属性自行研究
该部分源码证明上述拦截判定模块的
- 第1点
- 第6点
- 第9点部分证明
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// ViewGroup 自身消费事件
...
} 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) {
// 事件被子 View 消费(DOWN 事件)
handled = true;
} else {
// DOWN 之外事件处理逻辑
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;
}
}
复制代码
dispatchTouchEvent:遍历所有子 View(简述)
这部分源码是 ViewGroup 遍历所有子 View,让子 View 尝试消费事件的整体源代码。从下面的源代码可知:
- 对于同一系列事件(DOWN、MOVE、UP):只有 ACTION_DOWN 才能遍历所有子 View,其他事件不会遍历
- 子 View 不能接受事件的条件:该子 View 正在播放动画、焦点不在子 View 的区域内
- 跳出遍历的方式有两种:
- 直接从 mFirstTouchTarget 链表中找到可消费该事件的子 View
- 事件被某个子 View 消费
该部分源码讲解证明上述拦截判定模块的
- 第9点部分证明
该部分源码证明上述子 View 消费事件章节的
- 第15点
TouchTarget newTouchTarget = null;
if (!canceled && !intercepted) {
// 寻找方式:指寻找能够消费本事件的子 View 的方式
// 优化寻找(上):按官方注释讲解,是直接寻找可以接受该焦点的子 View
View childWithAccessibilityFocus = ...;
// 对于同一系列事件(ACTION_DOWN、ACTION_MOVE、ACTION_UP):只有 ACTION_DOWN 才能遍历所有子 View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
if (newTouchTarget == null && childrenCount != 0) {
...
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 优化寻找(下):直接找到能接受该焦点的子 View
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
...
}
// 不能接受事件:正在播放动画,或焦点不在子 View 的区域内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 寻找方式一:直接从 mFirstTouchTarget 链表中找到可消费该事件的子 View
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
...
break;
}
// 寻找方式二:事件被子 View 消费
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
break;
}
...
}
if (preorderedList != null) preorderedList.clear();
}
...
}
}
复制代码
dispatchTouchEvent:子 View 不能接受事件判定
注意:对于同一系列事件(DOWN、MOVE、UP):只有 ACTION_DOWN 才能遍历所有子 View,其他事件不会遍历
自行阅读相关方法:
- canViewReceivePointerEvents == true:正在播放动画
- isTransformedTouchPointInView == true:焦点不在子 View 的区域内
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
...
}
复制代码
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
复制代码
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
...
}
复制代码
dispatchTouchEvent:寻找方式一:直接从链表中找到可消费该事件的子 View
注意:对于同一系列事件(DOWN、MOVE、UP):只有 ACTION_DOWN 才能遍历所有子 View,其他事件不会遍历
如下源码可知:可以直接从 mFirstTouchTarget 链表中找到可消费该事件的子 View,并跳出循环
for (int i = childrenCount - 1; i >= 0; i--) {
...
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
...
}
}
复制代码
/**
* Gets the touch target for specified child view.
* Returns null if not found.
*/
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
复制代码
dispatchTouchEvent:寻找方式二:事件被子 View 消费(DOWN 事件)
注意:对于同一系列事件(DOWN、MOVE、UP):只有 ACTION_DOWN 才能遍历所有子 View,其他事件不会遍历
源码讲解:
- dispatchTransformedTouchEvent:实际就是调用子 View 的 dispatchTouchEvent 方法
- 事件被子 View 消费后,跳出循环
- addTouchTarget 将当前子 View 追加到 mFirstTouchTarget 链表的头部,注意一开始 mFirstTouchTarget == null
该部分源码证明上述拦截判定模块的
- 第2点
- 第8点部分证明
该部分源码证明上述子 View 消费事件章节的
- 第13点
- 第14点
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
复制代码
/**
* 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.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
...
handled = child.dispatchTouchEvent(event);
...
}
return handled;
}
复制代码
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
复制代码
同一系列事件分发简述
对简单 ViewGroup 而言
ACTION_DOWN 事件:
- 遍历子 View,事件被某个子 View 消费,将该子 View 存入链表中
- 遍历子 View,所有子 View 都没有消费,则链表为空,然后事件被 ViewGroup 消费
- 子 View 和 ViewGroup 都没有消费
ACTION_MOVE 事件(与上述一一对应):
- 不再遍历子 View,直接从链表中查找子 View,让链表中存储的子 View 消费事件
- 不再遍历子 View,因为链表为空,所以直接由 ViewGroup 尝试消费
- 同上
ACTION_UP 事件(与上述一一对应):
- 同 ACTION_MOVE 事件
- 同 ACTION_MOVE 事件
- 同 ACTION_MOVE 事件
对于复杂 ViewGroup 而言(比如 RecyclerView),ACTION_DOWN 事件被子 View 消费后,ACTION_MOVE 时会根据情况将链表清空。这样子 View 即不影响子 View 的点击事件,也不影响自身的滑动
六、滑动冲突
延期,待补充吧
requestDisallowInterceptTouchEvent:修改 mGroupFlags
结论:
- disallowIntercept == true:不允许 ViewGroup 直接拦截事件,必须先遍历子 View。
- disallowIntercept == false:mGroupFlags 修改为默认值
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
复制代码
参考
- 《Android 开发艺术探索》(任玉刚 著) 第三章 View 的事件体系
- Android 26 源代码(主要)
版本
- 2017-12-17:未完成版本
- 2017-12-23:完成对事件分发机制的说明
- ???:完成对滑动冲突的说明
声明
限于作者水平有限,出错难免,请积极拍砖! 欢迎任何形式的转载,转载请保留本文原文链接:juejin.im/post/5c1764…
结言
断断续续的把这篇博客一点点补全,感觉还是挺尴尬的,不过目前整体已经搞定了,剩下的边边角角,有时间再补充吧,捂脸。