上节概述
1、在 Activity 中的事件都会按照先交由 Activity 中处理 dispatchTouchEvent 。
2、然后在 Activity 会将事件上抛给 DecorView 的 superDispatchTouchEvent 去处理。
3、而实际上 superDispatchTouchEvent 方法调用的是 ViewGroup 的 dispatchTouchEvent 方法。
所以真正意义上的 dispatchTouchEvent 的过程即将真正开始。
查看上节文章View·从 InputEvent 到 dispatchTouchEvent 源码分析(二)。
俯瞰 dispatchTouchEvent 方法
先从整体的角度看下 dispatchTouchEvent 方法,它提供了:
- 对输入的 MotionEvent 事件的校对;
- 辅助相关功能;
- 事件分发;
- 返回值 handled;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1、输入事件的一致性校对
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// 2、若辅助功能已开启则正常启动事件分发,子类会捕获点击事件。
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
// 3、应用安全策略过滤触摸事件
if (onFilterTouchEventForSecurity(ev)) {
// 4、事件分发过程
}
if (!handled && mInputEventConsistencyVerifier != null)
{
// 5、输入一致性校对取消事件追踪
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
// 6、返回值 handled
return handled;
}
过滤事件,有选择的分发事件
当窗口被遮蔽时事件将会被过滤,即意味着不会执行事件分发。
/**
* 应用安全策略过滤触摸事件
*
* @param event 即将被过滤的事件对象
* @return 当事件允许被分发时返回 TRUE,否则为FALSE。
*
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// 当 Window 被遮蔽时,则事件将会被丢弃。
return false;
}
return true;
}
事件分发
因为事件分发的代码比较长,所以会按从上到下拆解代码进行分析。每段功能的代码段作为一个小节单独分析。
1、判断是否是新的事件流
1.1 ViewGroup#dispatchTouchEvent()
在代码中认定 ACTION_DOWN 是一段连续事件的开始的标志,所以代码接收到 ACTION_DOWN 事件时,会立即执行重置状态操作。
由于应用程序切换、ANR、或一些其他状态更改,框架可能会丢弃ACTION_UP或ACTION_CANCEL事件。
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
1.2 ViewGroup#cancelAndClearTouchTargets()
cancelAndClearTouchTargets
方法执行了首先检查了 mFirstTouchTarget
对象是否为空。如果 mFirstTouchTarget
不为空,意味着之前的事件流未清空。如果 mFirstTouchTarget
为空,也即意味着之前无事件流,所以也就无需再次释放了。
在mFirstTouchTarget
不为空为前提情况下传入的 event
为 null
,则会人工构建MotionEvent
对象并将事件类型设置为 ACTION_CANCEL
。有了事件对象后,接着会遍历 TouchTarget
的链,执行取消事件派发。在派发完成之后,同时清空 TouchTarget
的链。
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
// 人造事件
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
// 遍历派发事件
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
// 如果是人造事件,则及时释放
if (syntheticEvent) {
event.recycle();
}
}
}
在这里我们观察到事件的派发似乎与TouchTarget
与dispatchTransformedTouchEvent()
都有着紧密的关联。
取消事件的派发是通过 遍历 TouchTarget
的链,依次派发dispatchTransformedTouchEvent()
完成的。
所以我们跟进 TouchTarget
与 dispatchTransformedTouchEvent()
这两个方法,看看事件派发的细节具体是如何实现的。
1.3 TouchTarget
TouchTarget
内部提供了大小为32的可重用的链式结构,它把与触摸相关的目标( 内部就是View
)串联成起来,以便于后续的事件分发。
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// 可供触摸的子视图
public View child;
// 合并后的位掩码,
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// 列表中的下一个 target
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
1.4 添加 TouchTarget
每次调用 addTouchTarget
时,内部都会创建(或重用)TouchTarget
对象,然后将该TouchTarget
对象设置为列表的头部。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
1.5 谁调用了 addTouchTarget
追述到ViewGroup#dispatchTouchEvent()
方法,发现ViewGroup
遍历了它 子视图们
,并将它们构建成链。那么了解子视图们
与构建出的串的对应关系是件重要的事情。
// ViewGroup#dispatchTouchEvent
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
}
1.6 第一次事件(ACTION_DOWN)的分发过程
为了不偏离事件分发的主线,我们省略对 ViewTree 的代码分析。我们观察到上述代码在处理ACTION_DOWN
事件时候得到执行机会,也就是在收到ACTION_DOWN
事件时,框架会遍历View[]
。而View[]
的初始化可追述到addView()
方法。
所以上段代码最终表达了一件事情:遍历View[]
,并依据 View 拦截事件的返回值构建出一条处理链。重点请看方法dispatchTransformedTouchEvent()
。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
...
}
...
}
若已无父子关系(ViewGroup 中是没有 View)则由自身处理事件分发,否则交由子视图决定是否需要分发事件。
1.6.1 第一次事件(ACTION_DOWN)的分发过程之分发到 View
result
的true/false
,决定 View 是否能收到后续的手势事件,看到下方有几处会影响result
取值的地方。
onFilterTouchEventForSecurity(event)
(mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)
li.mOnTouchListener.onTouch(this, event)
onTouchEvent(event)
我们也就是为什么我们常用的
onTouchEvent(event)
与setTouchListener()
方法能影响到事件分发的根本原因。
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
final int actionMasked = event.getActionMasked();
...
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;
}
1.6.2 第一次事件(ACTION_DOWN)的分发过程之分发到 ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
...
if (onFilterTouchEventForSecurity(ev)) {
// 检查是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// ViewGroup 可以设置不接受拦截事件
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;
}
}
// 因为已经被拦截,所以 ACTION_DOWN 事件不会继续向下分发。
if (!canceled && !intercepted) {
...
}
// 因为没有执行 ACTION_DOWN 事件,所以 mFirstTouchTarget 也就没有得到初始化的机会。即最后由被拦截的 ViewGroup 自身处理的相关事件。
if (mFirstTouchTarget == null) {
// 没有mFirstTouchTarget,所以可以把自身当作一个普通视图。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
}
1.6.3 ACTION_DOWN 事件分发小结
至此为止,ACTION_DOWN
事件分发的轮廓已经描绘出来。从DecorView
到具体的得到焦点的 View
(如果中途被拦截,那就是拦截的 View),通过ACTION_DOWN
事件确定了一个 TouchTarget
分发链,那么接续的事件都将通过这条分发链进行处理。
图例说明
ACTION_DOWN 事件派发 确认 TouchTarget 的链 事件派发时本章总结
1、由 Stage 调用 DecorView.dispatchTouchEvent
2、DecorView 继承 ViewGroup 且未覆写 dispatchTouchEvent 方法,所以交由 ViewGroup的dispatchTouchEvent 进行处理。
3、首先判断是否是 ACTION_DOWN 事件,若是则展开初始化、释放等操作
4、其次判断是否需要 ViewGroup 拦截事件
4.1、若需要拦截,则 ViewGroup 视作 View 去处理派发。
4.2、若不需要拦截,则遍历 ViewGroup 下的子视图,依次询问子视图是否处理 dispatchTouchEvent,并将询问的轨迹以 TouchTarget 链的形式作记录。
4.3、直到确认到有子视图处理时,及终止 ACTION_DOWN 事件的派发。
5、接着事件会逐层 return
,直到回朔到DecorView
的实例中。接着由DecorView
的实例控制mFirstTouchTarget
对象展开事件分发。
6、…… 接下去的内容我们下一章节在续
网友评论