事件分发流程图
(该图片转自Gityuan的文章,重新作画)Android事件分发机制
该图调用逻辑可用如下伪代码表示:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
有如下一些结论:
-
如果一个 view 在 ACTION_DOWN 时没有消费事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 都不会交与此view处理
-
如果一个 view 在 ACTION_DOWN 时消费了事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 就直接交给此 view 处理
-
如果一个 ViewGroup 在一定条件下,拦截了 ACTION_MOVE 事件,那么此前消费事件的子 view 会收到一个 ACTION_CANCEL 事件,同时 onInteceptTouchEvent 在此事件序列中再不会被调用
-
...
但是,真的理解了这些结论从何而来了吗?恐怕没有一次深入的源码分析理解,是记不住的!
这次,我们结合View视图与源码来分析一次触屏(从 按下 -> 抬起 )的调用流程,源码分析将会以注释的形式展示。
应用视图层级
这是一张最简单的视图,在MainActivity中设置的布局,最外层的 ConstraintLayout ,还有我们自己添加的 MyViewgroup MyView 。
除此之外,还有还有系统添加的外层ViewGroup,整个视图的包含关系是:
ACTION_DOWN 的调用时序图:
该 ACTION_DOWN 事件会依次经过:
DecorView -> Activity -> PhoneWindow -> DecorView -> LinearLayout -> FrameLayout -> ActionBarOverlayLayout -> ContentFrameLayout -> ConstraintLayout -> MyViewgroup -> MyView
该调用过程是一个递归的过程,可由下图表示:
我们将结合源码分析 ACTION_DOWN ACTION_MOVE 两个事件的调用流程,其中我们重点分析 ViewGroup 的 dispatchTouchEvent 方法,
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 当触屏、按home、back等一些人为的操作,会调用此方法,这里就是触屏按下
onUserInteraction();
}
// 调用 PhoneWindow 的 superDispatchTouchEvent 方法
// 若 superDispatchTouchEvent 返回true(说明事件被底下的 View 消费了),
// 直接return true,不会再执行 Activity 的 onTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
// 直接调用 DecorView 的 superDispatchTouchEvent
return mDecor.superDispatchTouchEvent(event);
}
DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用父类 dispatchTouchEvent ,虽然 DecorView 继承自 FrameLayout
// 但 FrameLayout 没重写 dispatchTouchEvent,所以看 ViewGroup
return super.dispatchTouchEvent(event);
}
我们将会贴两遍 ViewGroup 的 dispatchTouchEvent() 方法,只不过注释只与当前事件相关
ACTION_DOWN
在MyView的区域按下,我们省略掉MyViewgroup之前的调用,即从 LinearLayout - ConstraintLayout,都是相同的递归调用,设定我们的递归调用已经到了MyViewgroup
ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
// 过滤事件,如果window没有被遮挡,就继续事件分发
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
// 这里 & MotionEvent.ACTION_MASK(取低8位) 是因为支持多点触控,事件就会分的更细:
// 第一根手指按下产生事件:ACTION_DOWN
// 第二根手指按下产生事件:ACTION_POINTER_DOWN
// 此时抬起一根手指产生事件:ACTION_POINTER_UP
// 再抬起另一根手指产生事件:ACTION_UP
// 通过 ev.getAction() 得到的值包含了 动作(低8位)、触控点索引(9-16位)等,而不单单是上述的几种行为动作,
// 你不能 if (ev.getAction() == ACTION_POINTER_DOWN) 这样比对,因为ACTION_POINTER_DOWN只是 ev.getAction() 的一部分
// 那么现在需要知道当前 MotionEvent 的action是何种类型,就需要从 ev.getAction()
// 返回的值里面剥离开来,所以加了 & ACTION_MASK 过滤掉其他信息,只取低8位,此举与 ev.getActionMasked 等效
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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.
// 这里是 ACTION_DOWN 事件,清除所有的TouchTargets,重置标志位,新的开始
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
// 这里是 ACTION_DOWN,mFirstTouchTarget 为null,因为一遍递归还没走完
// 还没有找到消费事件的 TouchTarget,为什么叫firstTouchTarget,因为有多点触控的情况,这里的first
// 指的是消费第一根手指落下的事件的子view,TouchTarget 是链表结构,链接多个都能消费事件的 子 view
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果该 ViewGroup 的child没有调用requestDisallowInterceptTouchEvent,判断是否要拦截
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.
// 若不是 ACTION_DOWN ,并且子 view 没有消费(mFirstTouchTarget == null),就直接拦截,这里是 ACTION_DOWN 事件,不会走到这里
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
// 检查当前是否是取消事件,当前是 ACTION_DOWN ,canceled为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
// 把事件拆分,因为可能是多点触控,需要将事件拆分给不同的 TouchTargets,这里split为true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 不是取消事件并且没有拦截,才会走到这里
// 这里 childWithAccessibilityFocus 为 null
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
// 只有在以下几种情况才去寻找能消费的子view,这里是 ACTION_DOWN ,往if里走
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 获取当前MotionEvent的索引,什么意思呢?
// 因为支持多点触控,那么就需要知道当前这个 MotionEvent 是由哪个触控点产生的
// 那么这个触控点信息,肯定需要包含在这个 MotionEvent 中,就在action的9-16位中,
// action是32位int值,将9-16位取出,再右移8位,得到的就是这个 actionIndex
// 当前就一根手指,actionIndex = 0,若是第二根手指按下,actionIndex = 1
final int actionIndex = ev.getActionIndex(); // always 0 for down
// idBitsToAssign = 1
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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.
// 根据子view 的z轴值的大小,先排个序,z轴越大的,优先安排扫描
// 若当前子view中,没有设置了z轴的view,那么这个 preorderedList 为null,这里为null
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
// 当前 preorderedList 为null,这里 customOrder 为 false,也就是没有自定义扫描顺序,按默认的来
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 循环查找,从外往里扫描,可以理解成,一个ViewGroup中
// 有多个子view,从布局中后写的view开始扫描,因为后写的view的区域可能会
// 覆盖先写的view,例如一个FrameLayout里,写在下面的view就会覆盖
// 到上面的view,这样先扫描覆盖在上面的view符合我们的视觉模型
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// 结合 preorderedList childIndex 来得出优先扫描的view,这里就是按照正常顺序
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
// 这里 childWithAccessibilityFocus 为 null
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 判断当前child能否接受事件,并且手指按下的点落在child区域内,如果有一样不符合,那就继续循环下一个子view,我们这里是符合条件的
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 如果之前已经有子view消费了事件,那么 mFirstTouchTarget 不为null,
// 而当前事件是多点触控产生的(第二根手指按下),那么这里去比对 mFirstTouchTarget 中的child属性
// 与 当前的child,是同一个的话,就复用,只是把 pointerIdBits 更新下
// 我们这里 newTouchTarget 为null,ACTION_DOWN 的递归还没走完呢, mFirstTouchTarget 肯定为null
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;
}
resetCancelNextUpFlag(child);
// 这里才是真正去分发事件的地方,也是在这个方法里去进行下一次递归的,下文中会专门分析这个方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 走到这里面,说明子view已经消费了事件,之前我们已经设定,当前已经递归到了MyViewgroup,
// 所以上面的 dispatchTransformedTouchEvent 里面就剩下一次调用了,那就是调用MyView的dispatchTouchEvent,
// MyView消费了 ACTION_DOWN 事件,执行到这里已经跳出了最后一次递归,当前 ViewGroup 是 MyViewGroup ,
// 也即当前 this 对象 是 MyViewGroup
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 这里面会给 mFirstTouchTarget 赋值,我们这里的话,就是MyView了,
// mFirstTouchTarget 的 child 会指向 MyView
// 那么,现在找到最终的消费事件的view了:MyView
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 已经被消费标志 置为true,下面要用
alreadyDispatchedToNewTouchTarget = true;
// 找到消费的子view了,就不再找了,跳出循环
// 注意,当前是在遍历 MyViewgroup 的循环中,要时刻注意当前的递归调用栈
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
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;
}
}
}
// Dispatch to touch targets.
// 如果没有找到一个能消费的子view,没有找到的话,这里 mFirstTouchTarget 才为null,我们这里找到了:MyView
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 那就把自己当成普通的view,而不是 ViewGroup,把事件分给自己,看看自己是否消费
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 如果已经有了消费的子view,那就分发给这个子view
// 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;
// 上面我们已经看到 alreadyDispatchedToNewTouchTarget 为true
// 上面的addTouchTarget()方法中 mFirstTouchTarget
// 与 newTouchTarget 指向同一个对象,所以相等,直接走if
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;
}
}
...
// 最后将结果返回给上一级调用者(父view,我们这里的话父View是ConstraintLayout),之后也是一样,跳出递归,层层返回
return handled;
}
ACTION_MOVE
public boolean dispatchTouchEvent(MotionEvent ev) {
// 我们还是设定当前已经递归到了 MyViewgroup,当前ViewGroup 为 MyViewgroup
...
boolean handled = false;
// 过滤事件,如果window没有被遮挡,就继续事件分发
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
// 现在是 ACTION_MOVE 事件,这里不走
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();
}
// Check for interception.
final boolean intercepted;
// 这里是 ACTION_MOVE,mFirstTouchTarget 不为null,因为在 ACTION_DOWN 时,
// MyViewgroup 的 mFirstTouchTarget 已经赋了值,就是 MyView 的包装TouchTarget 对象,所以这里的if还是要走的
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果该 ViewGroup 的child没有调用requestDisallowInterceptTouchEvent,判断是否要拦截
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
// 检查当前是否是取消事件,当前是 ACTION_MOVE ,canceled为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
// 把事件拆分,因为可能是多点触控,需要将事件拆分给不同的 TouchTargets,这里split为true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 不是取消事件并且没有拦截,才会走到这里
// 这里 childWithAccessibilityFocus 为 null
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
// if语句块里的作用是在 ACTION_DOWN 的时候找到消费事件的子view
// 这里是 ACTION_MOVE 不走if
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
// idBitsToAssign = 1
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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.
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 there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
// 这里 childWithAccessibilityFocus 为 null
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
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;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
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;
}
}
}
// 如果没有找到一个能消费的子view,没有找到的话,这里 mFirstTouchTarget 才为null,我们在 ACTION_DOWN 时 已经找到了:MyView
// 注意:按照我们当前的视图
// DecorView 的 mFirstTouchTarget 是 LinearLayout
// LinearLayout 的 mFirstTouchTarget 是 FrameLayout
// FrameLayout 的 mFirstTouchTarget 是 ActionBarOverlayLayout
// ActionBarOverlayLayout 的 mFirstTouchTarget 是 ContentFrameLayout
// ContentFrameLayout 的 mFirstTouchTarget 是 ConstraintLayout
// ConstraintLayout 的 mFirstTouchTarget 是 MyViewGroup
// MyViewGroup 的 mFirstTouchTarget 是 MyView
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 如果已经有了消费的子view,那就分发给这个子view
// 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;
// alreadyDispatchedToNewTouchTarget 为false,newTouchTarget 为null,
// 因为现在是 ACTION_MOVE 事件分发流程,上面该赋值的地方压根没走
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 走这里
// 一般当 ViewGroup 拦截了事件,cancelChild 为true,同时之前消费事件的子view会收到 ACTION_CANCEL 事件,
// 这里 cancelChild 为 false,因为我们没有拦截
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 这里进行的真正事件分发,第三个参数就是 ACTION_DOWN 时记录的 TouchTargets,我们这里是单点触摸,所以就是 mFirstTouchTarget
// 所以就有那句结论:在 ACTION_DOWN 时,谁消费了事件,之后的 MOVE、UP等事件都交给它
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;
}
}
...
// 最后将结果返回给上一级调用者(父view,我们这里父View是ConstraintLayout)
return handled;
}
ACTION_UP 类似
分析dispatchTransformedTouchEvent
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();
// cancel 相关
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;
}
// Calculate the number of pointers to deliver.
// 多点触控相关,这里 oldPointerIdBits = 1, desiredPointerIdBits = 1,newPointerIdBits = 1,说明事件不需要拆分,因为这里没有多点触控
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
// 如果当前 ViewGroup 没有子 view ,则交给自己处理,调用父类 dispatchTouchEvent 方法
// 也即 View 的 dispatchTouchEvent 方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 调用子 view 的 dispatchTouchEvent 方法,这里是进入下一级递归的入口,
// 若该子 view 是 ViewGroup 则继续递归调用,若该子 view 是 View,则此调用将是最后一级递归
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
// 若是多点触控,将事件拆分,这里未做过多研究
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
// 若上面未进行return,这里将转化过的 transformedEvent 再继续分发
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;
}
关于TouchTarget
可参考 ViewGroup事件分发总结-TouchTarget
总结
分发事件其实就是通过 dispatchTouchEvent 方法,onInterceptTouchEvent onTouchEvent 只不过是用来处理事件的,通过递归调用,来确定事件分发对象。
现在结合本例,再来看前文的几个结论的由来:
- 如果一个 view 在 ACTION_DOWN 时没有消费事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 都不会交与此view处理
比如 MyView 在 ACTION_DOWN 时没有消费事件,那么递归调用返回到 MyViewgroup 时,MyViewgroup 的 mFirstTouchTarget 就为null,在 ACTION_MOVE 到来时,会直接判断 mFirstTouchTarget 为null,走到 if(mFirstTouchTarget == null) 语句中,交给自己处理,这还是在 MyViewgroup 处理了 ACTION_DOWN 的前提下,如果 ACTION_DOWN 时,没有一个 view 消费,那么之后的 ACTION_MOVE ACTION_UP 到 DecorView 就停止分发了,因为没人消费 ACTION_DOWN 致使 每一层 ViewGroup 的 mFirstTouchTarget 都为null,当 ACTION_MOVE 到 DecorView 时,由于 mFirstTouchTarget == null ,还是会交给自己处理,所以这些事件根本就不会到达下面的 view了
- 如果一个 view 在 ACTION_DOWN 时消费了事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 就直接交给此 view 处理
与上面的相反,走 if(mFirstTouchTarget == null)的 else 语句快,并且在分发时,dispatchTransformedTouchEvent 第三个参数直接传的是记录的 mFirstTouchTarget
- 如果一个 ViewGroup 在一定条件下,拦截了 ACTION_MOVE 事件,那么此前消费事件的子 view 会收到一个 ACTION_CANCEL 事件,同时 onInteceptTouchEvent 在此事件序列中再不会被调用
- ...