关于ViewGroup中dispatchTouchEvent的源码分析
事件分发流程图
一些结论:
1.如果一个view在ACTION_DOWN时没有消费事件,那么同一事件序列之后的ACTION_MOVE ACTION_UP都不会交由此view处理
2.如果一个view在ACTION_DOWN时消费了事件,那么同一事件序列之后的ACTION_MOVE ACTION-UP都由此view处理
3.如果一个ViewGroup在一定条件下,拦截了ACTION_MOVE事件,那么此前消费事件的子view会收到一个ACTION_CANCEL事件,同时onInteceptTouchEvent在此事件序列不会再调用
4.ViewGroup中可以进行事件分发以及和事件处理,View主要负责事件处理
分析事件的视图层级关系:
ACTION_DOWN的调用时序图
事件经过流程:
DecorView -> Activity -> PhoneWindow -> DecorView -> LinearLayout -> FrameLayout -> ActionBarOverlayLayout -> ContentFrameLayout -> ConstraintLayout -> MyViewgroup -> MyView
递归调用流程:
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);
}
针对具体的事件分析,在上面层级模型的情况下处理事件分析,这里以ACTION_DOWN开始:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//
boolean handled = false;
//进行事件进行安全过滤,根据源码看基本上就是处理当view被遮挡时不响应事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
/**
这里的& MotionEvent.ACTION_MASK(取低8位),因为支持多点触摸,最大可响应的多点触摸32个点
第一个手指按下的事件: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()返回的值里剥离出来,所以加了& MotionEvent.ACTION_MASK过滤其他信息,只取低8位,此举与ev.getActionMasked等效。
**/
// 获取事件类型。action的值高8位会包含该事件触摸点索引信息,actionMasked为干净的事件类型,
// 在单点触摸情况下action和actionMasked无差别。
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//不论是点击、长按、滑动事件的第一个事件动作一定是ACTION_DOWN
//当是ACTION_DOWN时取消和清除所有的触摸目标,重置触摸状态
//TouchTarget是一个链标结构,链接了多个能够消费事件的子view
if (actionMasked == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN表示一次全新的事件序列开始,那么清除旧的
// TouchTarget(正常情况下TouchTarget在上一轮事件序列结束时会清
// 空,若此时仍存在,则需要先给这些TouchTarget派发ACTION_CANCEL事
// 件,然后再清除),重置触摸滚动等相关的状态和标识位。
// 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_DOWN,mFirstTouchTarget为null,因为一遍递归还没走完,没有找到事件的消费的事件的View,也就是TouchTarget,为什么叫mFirstTouchTarget,是因为在多点触摸时这里的first指的是消费第一根手指落下事件的view
**/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 判断child是否抢先调用了requestDisallowInterceptTouchEvent方法
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//因为这里分析的是ViewGroup中的事件分发,所以就会有涉及到子view调用requestDisallowInterceptTouchEvent,设置事件拦截
// 再通过onInterceptTouchEvent方法判断(子类可重写)
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),就直接拦截
// 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_CANCEL事件,当前是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;
// newTouchTarget用于保存新的派发目标
TouchTarget newTouchTarget = null;
// 标记在目标查找过程中是否已经对newTouchTarget进行过派发
boolean alreadyDispatchedToNewTouchTarget = false;
// 只有当非cancele且不拦截的情况才进行目标查找,否则直接跳到执行派发步骤。如果是
// 因为被拦截,那么还没有派发目标,则会由ViewGroup自己处理事件
if (!canceled && !intercepted) {
//不是取消,并且没有拦截事件进入这里处理
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
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
// 通过触摸点索引取得触摸点ID,然后左移x位(x=ID值)
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.
// 遍历mFirstTouchTarget链表,进行清理。若有TouchTarget设置了此触摸点ID,
// 则将其移除该ID,若移除后的TouchTarget已经没有触摸点ID了,那么接着移除
// 这个TouchTarget。
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.
//一个ViewGroup会包含多个view,那么先找哪个子view去进行事件的传递呢?根据人的理解因该是最上层的view响应,那么这里先根据z轴的值进行排序
//若是当前的子view中没有设置z轴的话,这里的preorderedList位null,这里就为null
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
/**
这里就是循环查找,从外往里扫描,可以理解为,一个ViewGroup中有多个子view,从布局中的后写的view开始扫描,因为在很多布局的设置中,后写的view会覆盖遮挡到前面的view,那么在上面的view应该是要先响应点击事件的
*/
for (int i = childrenCount - 1; i >= 0; i--) {
//结合前面查到的z轴排序的结合获取view,这里的preorderedList是null,所以正常获取在child集合中的下标位置
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.
//因为这里是ACTION_DOWN,第一次的循环还没走完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;
}
/**
这里是通过mFirstTouchTarget来对比,如果之前已经有view消费了事件,那么mFirstTouchTarget必定是不为null的,那么事件应该是第二根手指落下的事件,这里就是对比当前的view和mFirstTouchTarget中的view比较,如果是同一个view,就复用,只是把pointerIdBits更新下
newTouchTarget为null,因为我们还没有view消费事件,mFirstTouchTarget还为null
*/
newTouchTarget = getTouchTarget(child);
// 遍历mFirstTouchTarget链表,查找该child对应的TouchTarget。
// 如果之前已经有触摸点落于该child中且消费了事件,这次新的触摸点也落于该child中,
// 那么就会找到之前保存的TouchTarget。
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
// 派发目标已经存在,只要给TouchTarget的触摸点ID集合添加新的
// ID即可,然后退出子view遍历。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//这里才是真正去分发事件的地方,也就是在这个方法里进行下一次递归
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
/**
如果方法进入这里,说明子view消费了事件,当前的递归层级view为MyViewgrou,所以dispatchTransformedTouchEvent的向下调用就是,MyView的dipatchTouchEvent,MyView消费ACTION_DOWN事件,递归跳出,当前的ViewGroup就是MyViewGroup
*/
// 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();
//这里就给mFirstTouchTarget设置值,也就是有事件消费的View了,并将其设置为新的头节点
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//是否已经消费标志
alreadyDispatchedToNewTouchTarget = true;
//这的循环层级是在MyViewGroup中,找到消费的子view,就不再找了,跳出循环
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.
// 若没有找到派发目标(没有命中child或命中的child不消费),但是存在
// 旧的TouchTarget,那么将该事件派发给最开始添加的那个TouchTarget,
// 多点触摸情况下有可能这个事件是它想要的。
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
//如果没有子view消费事件,mFirstTouchTarget为null,但是这里MyView消费了,所以mFirstTouchTarget不为null
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//如果子view没有能消费的,那么就把事件分配给自己,看看自己能消费吗
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} 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;
//判断当前的alreadyDispatchedToNewTouchTarget处理标识和newTouchTarget和mFirstTouchTarget是否为同一个对象,这是里直接走if
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
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);
}
//将结果返回给上一层调用者(父view,我们这里是MyViewGroup,它的父View是ConstraintLayout),之后的层层返回,跳出递归
return handled;
}
ACTION_MOVE,其他的事件就类似了
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//
boolean handled = false;
//进行事件进行安全过滤,根据源码看基本上就是处理当view被遮挡时不响应事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
/**
这里的& MotionEvent.ACTION_MASK(取低8位),因为支持多点触摸,最大可响应的多点触摸32个点
第一个手指按下的事件: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()返回的值里剥离出来,所以加了& MotionEvent.ACTION_MASK过滤其他信息,只取低8位,此举与ev.getActionMasked等效。
**/
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//这里是ACTION_MOVE事件,所以不会清除之前的事件状态什么的,也就是mFirstTouchTarget保存有ACTION_DOWN的处理view
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;
/**
因为已经有view响应事件ACTION_DWON,所以mFirstTouchTarget != null
**/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//因为这里分析的是ViewGroup中的事件分发,所以就会有涉及到子view调用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),就直接拦截
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_CANCEL事件,当前是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) {
//不是取消,并且没有拦截事件进入这里处理
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//只有下面事件才会去找能够消费的子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.
//一个ViewGroup会包含多个view,那么先找哪个子view去进行事件的传递呢?根据人的理解因该是最上层的view响应,那么这里先根据z轴的值进行排序
//若是当前的子view中没有设置z轴的话,这里的preorderedList位null,这里就为null
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
/**
这里就是循环查找,从外往里扫描,可以理解为,一个ViewGroup中有多个子view,从布局中的后写的view开始扫描,因为在很多布局的设置中,后写的view会覆盖遮挡到前面的view,那么在上面的view应该是要先响应点击事件的
*/
for (int i = childrenCount - 1; i >= 0; i--) {
//结合前面查到的z轴排序的结合获取view,这里的preorderedList是null,所以正常获取在child集合中的下标位置
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.
//因为这里是ACTION_DOWN,第一次的循环还没走完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;
}
/**
这里是通过mFirstTouchTarget来对比,如果之前已经有view消费了事件,那么mFirstTouchTarget必定是不为null的,那么事件应该是第二根手指落下的事件,这里就是对比当前的view和mFirstTouchTarget中的view比较,如果是同一个view,就复用,只是把pointerIdBits更新下
newTouchTarget为null,因为我们还没有view消费事件,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)) {
/**
如果方法进入这里,说明子view消费了事件,当前的递归层级view为MyViewgrou,所以dispatchTransformedTouchEvent的向下调用就是,MyView的dipatchTouchEvent,MyView消费ACTION_DOWN事件,递归跳出,当前的ViewGroup就是MyViewGroup
*/
// 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();
//这里就给mFirstTouchTarget设置值,也就是有事件消费的View了
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//是否已经消费标志
alreadyDispatchedToNewTouchTarget = true;
//这的循环层级是在MyViewGroup中,找到消费的子view,就不再找了,跳出循环
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走else,相当于一个事件链中的事件都会交由一个view进行处理!!!
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//如果子view没有能消费的,那么就把事件分配给自己,看看自己能消费吗
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} 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;
//因为上面的if没有走,所以alreadyDispatchedToNewTouchTarget为false,走else
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
/**
一般只有ViewGroup拦截了事件,cancelChild为true,同时之前消费事件的view会收到ACTION_CANCEL事件,
这里的cancelChild为false
*/
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//事件分发,可以看到target就是mFirstTouchTarget,还是之前响应事件的那个view
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
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);
}
//将结果返回给上一层调用者(父view,我们这里是MyViewGroup,它的父View是ConstraintLayout),之后的层层返回,跳出递归
return handled;
}
dispatchTransformedTouchEvent
/**
* 处理事件分发
* 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) {
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();
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方法,自己进行处理
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
/**
调用child的dispatchTouchEvent,进入下一层级的递归,如果child也是ViewGroup那么继续往下,直到有消费或找到最后层级的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;
}
事件分发就是通过dispatchTouchEvent,onInterceptTouchEvent onTouchEvent只不过是用来处理事件的,通过递归函数调用,来确定事件处理对象
分析前面的几个结论:
1.如果一个view在ACTION_DOWN时没有消费事件,那么同一事件序列之后的ACTION_MOVE ACTION_UP都不会交由此view处理
ViewGroup是通过mFirstTouchTarget来确定事件处理view的,如果没有事件处理,那么这个mFirstTouchTarget为null,ViewGroup就会自己来处理,例如上面的布局层级中,如果MyView
没有处理事件,MyViewGroup处理了相应的事件,那么在判断mFirstTouchTarget自己就处理了,这是在MyViewGroup消费事件的前提下,如果都没有View消费事件,那么这个会返回到DecorView,DecorView中的mFirstTouchTargte为null根本就不会再往下分发了。
2.如果一个view在ACTION_DOWN时消费了事件,那么同一事件序列之后的ACTION_MOVE ACTION-UP都由此view处理
事件分发处理中如果mFirstTouchTarget不为null,那么就让分发到的就是mFirstTouchTarget
3.如果一个ViewGroup在一定条件下,拦截了ACTION_MOVE事件,那么此前消费事件的子view会收到一个ACTION_CANCEL事件,同时onInteceptTouchEvent在此事件序列不会再调用
TouchTarget源码分析
private static final class TouchTarget {
// ···
// 消费事件的view
@UnsupportedAppUsage
public View child;
// child接收的触摸点的id集合
public int pointerIdBits;
// 指向链表的下一个节点
public TouchTarget next;
// ···
}
根据TouchTarget来看ViewGroup整体的事件分发,整个流程中只有事件序列开始或子序列开始时(ACTION_DOW或ACTION_POINTER_DOWN),会遍历子view,并将目标封装成TouchTarget保存在mFirstTouchTarget链表中,完成派发目标查找后,再遍历TouchTarget链表,依次进行事件派发。
附加问题:当ViewGroup在ACTION_MOVE时,满足一定条件,拦截子View的事件,为什么onInterceptTouchEvent一旦返回true,就再也不会被调用?
注意intercepted,如果onInterceptTouchEvent返回true,那么会导致cancelChild为true,那么mFirstTouchTarget会进行mFirstTouchTarget = next操作,因为是单点触控,next节点是null,所以mFirstTouchTarget就被赋值为null,再看onInterceptTouchEvent的调用时机
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 判断child是否抢先调用了requestDisallowInterceptTouchEvent方法
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//因为这里分析的是ViewGroup中的事件分发,所以就会有涉及到子view调用requestDisallowInterceptTouchEvent,设置事件拦截
// 再通过onInterceptTouchEvent方法判断(子类可重写)
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),就直接拦截
intercepted = true;
}
后续的事件不会是ACTION_DOWN,并且mFirstTouchTarget为null了,这里就会走else逻辑,调用不到onInterceptTouchEvent方法了。