接着上篇《Activity对触摸事件的分发流程》,这篇我们讲《ViewGroup和View对触摸事件的分发流程》。
1、ViewGroup对触摸事件的分发流程
ViewGroup.dispatchTouchEvent()
首先是Activity中dispatchTouchEvent()方法return super之后将会调用ViewGroup的dispatchTouchEvent()方法:
1、如果onInterceptTouchEvent()方法返回false,将通过buildTouchDispatchChildList()方法获取所有能接收该事件的子视图;而后如果dispatchTransformedTouchEvent()方法返回true,将通过addTouchTarget()生成mFirstTouchTarget对象。
2、如果mFirstTouchTarget为空,说明此时子视图为View,将通dispatchTransformedTouchEvent()继续分发事件;如果mFirstTouchTarget不为空,说明此时子视图为ViewGroup,将遍历ViewGroup的子视图通过dispatchTransformedTouchEvent()继续分发事件。
3、dispatchTransformedTouchEvent()方法将通过View的dispatchTouchEvent()方法继续分发事件。
ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//为了安全性过滤触摸事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
// 0xff 动作代码中动作本身部分的位掩码。属于多点触碰。
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 处理最初的DOWN事件。
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 当开始一个新的触摸手势时扔掉所有以前的状态。
// 由于应用程序切换、ANR或其他一些状态变化,框架可能已经放弃了前一个手势的up或cancel事件。
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查拦截。
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//TODO 1、当返回true,表示该事件被当前视图拦截;当返回false,继续执行事件分发。
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); //恢复操作,以防它被更改
} else {
intercepted = false;
}
} else {
// 没有触摸目标,这个动作不是初始向下,所以这个ViewGroup继续拦截触摸。
intercepted = true;
}
// 如果被拦截,启动正常的事件分发。另外,如果已经有一个处理手势的视图,则执行正常的事件分派。
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 检查取消。
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 如果需要,更新指针指向的触摸目标列表。
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
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(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清理这个指针id的早期触摸目标,以防它们不同步。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//TODO 2、从视图最上层到下层,获取所有能接收该事件的子视图
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 如果有一个具有可访问性焦点的视图,我们希望它首先获得事件,如果不处理,我们将执行正常的分派。
// 我们可以进行两次迭代,但是考虑到时间范围,这样做更安全。
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 孩子已经在其范围内接受触摸。
// 除了它正在处理的指针之外,再给它一个新的指针。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// TODO 3、分发触摸事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 孩子想在自己的范围内接受触摸。
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex点成预分类列表,查找原始索引
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// TODO 4、添加触摸目标
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 可访问性焦点没有处理事件,因此清除标记并对所有孩子进行正常分派。
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 没有找到一个孩子来接收这个事件。
// 将指针分配给最近添加的最少的目标。
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 分发给触摸目标。
if (mFirstTouchTarget == null) {
// TODO 3、分发触摸事件。没有接触目标,所以把这当作一个普通的View。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 发送给触摸目标,不包括新的触摸目标,如果我们已经发送给它。必要时取消接触目标。
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;
//TODO 3、分发触摸事件
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;
}
}
// 更新列表的触摸目标指针或取消,如果需要。
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);
}
}
return handled;
}
ViewGroup.onInterceptTouchEvent()
ViewGroup.java
/**
* 实现此方法来拦截所有触摸屏动作事件。这允许您在事件发送给孩子时查看它们,并在任何时候获得当前动作的所有权。
* 使用这个方法需要谨慎,因为它与View.onTouchEvent(MotionEvent)的交互相当复杂,使用它需要以正确的方式实现这两个方法。事件将按以下顺序接收:
* 1、您将在这里接收down事件。
* 2、down事件将由这个视图组的一个子视图组处理,或者交给您自己的onTouchEvent()方法来处理;这意味着您应该实现onTouchEvent()来返回true,这样您将继续看到手势的其余部分(而不是寻找父视图来处理它)。另外,通过从onTouchEvent()返回true,您将不会在onInterceptTouchEvent()中收到任何后续事件,并且所有的触摸处理都必须在onTouchEvent()中正常发生。
* 3、只要从这个函数返回false,接下来的每个事件(直到并包括最终的up)都将首先在这里传递,然后传递到目标的onTouchEvent()。
* 4、如果您从这里返回true,您将不会接收到任何以下事件:目标视图将接收到相同的事件,但是带有action MotionEvent#ACTION_CANCEL,并且所有进一步的事件将被传递到您的onTouchEvent()方法,并且不再出现在这里。
*
* @param ev 要沿层次结构向下分派的触摸事件。
* @return 返回true从子元素中窃取动作事件,并通过onTouchEvent()将它们分配给这个ViewGroup。当前目标将接收一个ACTION_CANCEL事件,这里不再传递任何消息。
*/
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;
}
ViewGroup.buildTouchDispatchChildList()
ViewGroup.java
/**
* 提供视图的自定义顺序,在其中分配触摸。这是在紧密循环中调用的,因此不允许分配对象,包括返回数组。相反,您应该返回一个预先分配的列表,该列表将在分派完成后被清除。
* @hide
*/
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
ViewGroup.java
/**
* 填充(并返回)mPreSortedChildren一个视图的子元素的预定列表,首先按Z排序,然后按子元素绘制顺序排序(如果适用的话)。此列表在使用后必须清除,以避免泄漏子视图。使用一个稳定的插入排序,通常是O(n)的viewgroup很少上升的孩子。
*/
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++) {
// 将下一个子元素(按子元素顺序)添加到列表的末尾
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
// 在所有Z值较大的视图之前插入
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
ViewGroup.dispatchTransformedTouchEvent()
ViewGroup.java
/**
* 将触摸事件转换为特定子视图的坐标空间,筛选不相关的指针id,并在必要时覆盖其操作。如果child为空,则假设MotionEvent将被发送到这个ViewGroup。
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 取消触摸是一种特殊情况。我们不需要执行任何转换或过滤。重要的是行动,而不是内容。
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;
}
// 计算要传递的指针的数量。
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// 如果由于某种原因,我们最终处于不一致的状态,看起来我们可能会产生一个没有指针的触摸事件,那么就删除该事件。
if (newPointerIdBits == 0) {
return false;
}
// 如果指针的数量是相同的,并且我们不需要执行任何复杂的不可逆转换,那么我们可以为这个分派重用motion事件,只要我们小心地还原我们所做的任何更改。否则我们需要复印一份。
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//TODO * 分发触摸事件
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
//TODO * 分发触摸事件
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 执行任何必要的转换和分派。
if (child == null) {
//TODO * 分发触摸事件
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());
}
//TODO * 分发触摸事件
handled = child.dispatchTouchEvent(transformedEvent);
}
// 结束
transformedEvent.recycle();
return handled;
}
ViewGroup.addTouchTarget()
ViewGroup.java
/**
* 将指定子对象的触摸目标添加到列表的开头。假设目标子节点不存在。
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
2、View对触摸事件的分发流程
1、先由OnTouchListener的OnTouch()来处理事件,当返回true,则消费该事件,否则由onTouchEvent处理事件;
2、当onTouchEvent返回true时,消费该事件,否则继续分发该事件。
View.dispatchTouchEvent()
View.java
/**
* 将触摸屏触摸事件向下传递到目标视图,如果是目标视图,则传递到该视图。
* @param event 要分发的触摸事件。
* @return 如果事件是由视图处理的,则为真,否则为假。
*/
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在Down事件之前,如果存在滚动操作则停止。不存在则不进行操作
stopNestedScroll();
}
// mOnTouchListener.onTouch优先于onTouchEvent。
if (onFilterTouchEventForSecurity(event)) {
//当存在OnTouchListener,且视图状态为ENABLED时,调用onTouch()方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; //如果已经消费事件,则返回True
}
//如果OnTouch()没有消费Touch事件则调用OnTouchEvent()
if (!result && onTouchEvent(event)) { // [见小节2.5.1]
result = true; //如果已经消费事件,则返回True
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 处理取消或抬起操作
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
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;
// 当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;
//与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
//这是Tap操作,移除长按回调方法
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//调用View.OnClickListener
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
//获取是否处于可滚动的视图内
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态,用来判断用户是否想要滚动。默认延时为100ms
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//当不再滚动视图内,则立刻反馈按压状态
setPressed(true, x, y);
checkForLongClick(0); //检测是否是长按
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
总结
方法 | 返回 | 解释 |
---|---|---|
Activity.dispatchTouchEvent() | true | 消费事件 |
Activity.dispatchTouchEvent() | false | 消费事件 |
Activity.dispatchTouchEvent() | super | 向下分发事件给ViewGroup.dispatchTouchEvent(),如果是DOWN事件,则触发onUserInteraction() |
ViewGroup.dispatchTouchEvent() | true | 消费事件 |
ViewGroup.dispatchTouchEvent() | false | 向上分发事件给Activity.onTouchEvent() |
ViewGroup.dispatchTouchEvent() | super | 分发事件给ViewGroup.onInterceptTouchEvent() |
Activity.onTouchEvent() | true | 消费事件 |
Activity.onTouchEvent() | false | 消费事件 |
Activity.onTouchEvent() | super | 消费事件 |
ViewGroup.onInterceptTouchEvent() | true | 分发事件给ViewGroup.OnTouchListener.onTouch() |
ViewGroup.onInterceptTouchEvent() | false | 向下分发事件给View.dispatchTouchEvent() |
ViewGroup.onInterceptTouchEvent() | super | 向下分发事件给View.dispatchTouchEvent() |
ViewGroup.OnTouchListener.onTouch() | true | 消费事件 |
ViewGroup.OnTouchListener.onTouch() | false | 分发事件给ViewGroup.onTouchEvent() |
ViewGroup.onTouchEvent() | true | 消费事件 |
ViewGroup.onTouchEvent() | false | 向上分发事件给Activity.onTouchEvent() |
ViewGroup.onTouchEvent() | super | 向上分发事件给Activity.onTouchEvent() |
View.dispatchTouchEvent() | true | 消费事件 |
View.dispatchTouchEvent() | false | 向下分发事件给View.OnTouchListener.onTouch() |
View.dispatchTouchEvent() | super | 向下分发事件给View.OnTouchListener.onTouch() |
View.OnTouchListener.onTouch() | true | 消费事件 |
View.OnTouchListener.onTouch() | false | 分发事件给View.onTouchEvent() |
View.onTouchEvent() | true | 消费事件 |
View.onTouchEvent() | false | 向上分发事件给ViewGroup.OnTouchListener.onTouch() |
View.onTouchEvent() | super | 消费事件 |
1、onInterceptTouchEvent返回值true表示事件拦截, onTouch/onTouchEvent 返回值true表示事件消费。
2、触摸事件先交由Activity.dispatchTouchEvent。再一层层往下分发,当中间的ViewGroup都不拦截时,进入最底层的View后,开始由最底层的OnTouchEvent来处理,如果一直不消费,则最后返回到Activity.OnTouchEvent。
3、ViewGroup才有onInterceptTouchEvent拦截方法。在分发过程中,中间任何一层ViewGroup都可以直接拦截,则不再往下分发,而是交由发生拦截操作的ViewGroup的OnTouchEvent来处理。
4、子View可调用requestDisallowInterceptTouchEvent方法,来设置disallowIntercept=true,从而阻止父ViewGroup的onInterceptTouchEvent拦截操作。
5、OnTouchEvent由下往上冒泡时,当中间任何一层的OnTouchEvent消费该事件,则不再往上传递,表示事件已处理。
6、如果View没有消费ACTION_DOWN事件,则之后的ACTION_MOVE等事件都不会再接收。
7、只要View.onTouchEvent是可点击或可长按,则消费该事件.
8、onTouch优先于onTouchEvent执行,onTouch的位置在onTouchEvent前面。当onTouch返回true,则不执行onTouchEvent,否则会执行onTouchEvent。onTouch只有View设置了OnTouchListener,且是enable的才执行该方法。
欢迎关注Android技术堆栈,专注于Android技术学习的公众号,致力于提高Android开发者们的专业技能!