前言
在对APP操作的过程中,会产生一系列的Touch事件,这些事件会按照一定规则分发到相应的View上的进行处理。这样的分发和处理过程简称事件分发。
理解事件分发算是基本功,否则难以处理各类与Touch有关的冲突问题。最新重新看了看事件分发,宏观来说,它的机制不难,但在细微处,在事件分发前后还做了一些有意思的处理。
概述
- 通过此文章能了解到事件分发机制
- 能了解事件分发机制中一些细节处理
源码版本为8.0
正文
关于事件分发
事件分发涉及到的核心函数如下:
- ViewGroup.dispatchTouchEvent() : 制定分发的规则
- ViewGroup.onInterceptTouchEvent() : 对Touch事件进行拦截
- ViewGroup.dispatchTransformedTouchEvent() : 协助进行分发
- View.dispatchTouchEvent() : 具体的分发响应
事件分发在事件上,是通过责任链来实现的,责任链这一设计模式自行百度,这里简单理解一下责任链,如图:
责任链即是对同一种事件感兴趣而链接起来的链条,链的每一个节点都有处理此类事件的能力。当事件到来时,逐级传递,而当某一节点处理后,不再向下传递,而是逐层向上进行反馈,如图中节点A。
对于事件分发来说,它的责任链属于不纯的。
机制
Touch事件到来时,由ViewGroup首先接收到事件,这一点能够理解,因为在View层级里,最外不始终为ViewGroup。
场景来到ViewGroup.dispatchTouchEvent()。高能预警:源码未做删减,对其它部分不敢兴趣可以直接定位到dispatchTransformedTouchEvent()前后
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// 辅助功能处理
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
// 处理状态
boolean handled = false;
/**
* 需要确保事件的安全性, 一般来说当前窗口被其它窗口覆盖时对此类事件存疑
*/
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 处理DOWN⌚️
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在处理新的手势事件时,先丢弃(处理)之前的事件
// 终止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) {
// 事件拦截
intercepted = onInterceptTouchEvent(ev);
// 恢复事件,防止被篡改
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 无可触碰的目标控件,并且不是初次的down事件, 拦截
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;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 以Z轴为序获取子View
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
// 获取view的索引
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
/**
* 让辅助功能的View做处理
*/
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
/**
* 子view要能接收pointer事件,并且此次事件落在它的区域内
*/
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);
/**
* 向子view分发事件,区分ViewGroup和View,深度遍历
* 进入则说明已经有子view进行了处理
*/
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 将此view加入TouchTarget链
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 到这里说明每个子view都接收到了这个事件
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//没有子View处理此次事件,让最新的TouchTarget处理
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// 没有可以接收事件的view,将自己当作view
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 再次向target链上的view分发事件
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;
}
}
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 (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
// 反馈
return handled;
}
总体上看,ViewGroup接收到事件后,会向Z轴序的View分发事件,也就是深度遍历,而当某一环节处理了此事件,循环终止,状态保存在handled并逐层反馈。在分发前要考虑到的一大因素是,当前ViewGroup是否要拦截事件,也可以理解为ViewGroup自己处理,拦截是通过ViewGroup.onInterceptTouchEvent()实现,而分发则是通过ViewGroup.dispatchTransformedTouchEvent().
此外,上面的解释中可以发现,在向子View分发事件前后,会对事件进行特别处理(不感兴趣可以略过)。其中的考虑因素如下:
- 辅助状态
- 事件的安全性
- 防止事件篡改
- 处理好之前的事件
- 事件拦截
- 事件取消
- 以Z轴为序为子View排序
- 子view的辅助状态
- 子view的接收状态
- 是否发生在子View区域上
- 事件交给特殊子view处理
- 没有子view处理,将自己当作view
- target链
挑几个有意思的简单了解下。
事件的安全性:通过onFilterTouchEventForSecurity(ev)进行排查,对于存疑的事件不进行处理。在这个筛选规则里面,有一个MotionEvent.FLAG_WINDOW_IS_OBSCURED事件,此事件的发生说明当前窗口被覆盖了,可以想到的场景如劫持,换言之,可做安全防护用。
处理好之前的事件:在对Touch事件进行处理需要先让之前的Touch事件处理完毕,否则会引起混乱。简单来理解就是,并不是每一次的Touch事件都要进行分发。
子View区域:这个容易理解,如果手指的点击区域没有落到一些View的区域上,自然不用向这些View分发。
target链:事件分发是会遍历两次的,其中一次会向target链分发,其中缘由未知,挺感兴趣,暂且记下。
......
事件拦截
public boolean onInterceptTouchEvent(MotionEvent ev)
ViewGroup拥有拦截事件的能力,通过onInterceptTouchEvent()触发,使用体现在自定义ViewGroup上,这里不做演示。
向子View分发
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) {
// 把自己当作view来对待
handled = super.dispatchTouchEvent(event);
} else {
// 向子view分发
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 计算pointer数量
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
/**
* 在某些情况下,没有pointer,丢弃,不做处理
*/
if (newPointerIdBits == 0) {
return false;
}
/**
* 一些情况下,pointer数量相同,直接复用,copy
*/
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
// 子元素为空或有单位矩阵
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// 将自己视为View
handled = super.dispatchTouchEvent(event);
} else {
// 获取在xy上的偏移量
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 向子View分发
handled = child.dispatchTouchEvent(event);
// 还原
event.offsetLocation(-offsetX, -offsetY);
}
// 回馈
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 和上面的逻辑类似
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;
}
dispatchTransformedTouchEvent()对具体的View分发事件,且根据事件的不同,Pointer数量的不同需要做不同处理。主要到child == null 的情况。在ViewGroup通过深度遍历将事件全部分发给子View后,均没有得到处理,此时ViewGroup会将自身视为View尝试处理,通过传参为null来达到目的,可以在ViewGroup.dispatchTouchEvent()查找dispatchTransformedTouchEvent()看到这一细节。
View处理Touch事件
public boolean dispatchTouchEvent(MotionEvent event) {
// 辅助功能
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
// 默认不处理
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
// DOWN事件处理,让view内嵌部分停止滑动
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
// 确保事件的安全性,可信事件才能继续
if (onFilterTouchEventForSecurity(event)) {
// 拖动scroll bar时候这里来处理
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
// 设置了OnTouchListener来处理事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// view自身的Touch逻辑,默认false
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 手势结束后view内嵌部分处理
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
事件被分发到了具体的View,对于View来说,进行处理也要考虑一些因素如:
- 辅助状态
- 内部滑动状态
- 事件的安全性
- OnTouchListener
- 手势结束
有些View的内部是具有滑动功能的,在View处理一些事件是,此类功能应做好处理。特别注意的是,如果通过View.setOnTounchListener()给View设置了Touch监听,则不回出发View.onTouchEvent()。在处理完后,View需向上级反馈。
总结
总体来说,事件的分发分为如下步骤:
- ViewGroup.dispatchTouchEvent()接收到事件
- 考虑ViewGroup.onInterceptTouchEvent()是否对事件进行拦截
- ViewGroup向子View进行拦截
- 子View通过View.dispatchTouchEvent()进行处理,并向上层反馈
- 子View已处理事件,ViewGroup不再分发,逐层反馈至顶层。否则重复此过程
分发图如下:
事件分发机制不难理解,通过深度遍历找到某一处理事件的节点即可。当然也存在都不进行处理的情况。
如果较真的话,通过查看事件分发前后的处理,可以发现事件分发不仅仅是将事件分发到具体的环节中,还需要对事件的各方面进行考虑。个人来说,对辅助状态、安全性、target链蛮有兴致,有机会做些研究。