以前对于触摸事件的分发过程有过专门的研究,当时把郭林大神讲ViewGroup触摸分发的博客(http://blog.csdn.net/guolin_blog/article/details/9153747)看了不下3边,当时感觉对于分发已经了然于心。但是,后来在项目实战中发现好多一想不到的问题,郭大神的博客完全没法解释,凸(艹皿艹 ),然后自己打开ide,看ViewGroup源码发现跟郭大神博客里面的源码完全不一样,原来郭大神的博客说得是2.x版本ViewGroup关于触摸的分发的源码,无奈,相当于以前的研究都白费了。正好马上就进行技术分享会,我直接报,我要说触摸分发的过程。也借此机会,可以好好研究一下触摸的分发,并且解开我的疑惑。
触摸分发和响应的过程都是在dispatchTouchEvent方法中完成的,所以,此文主要分析ViewGroup中dispatchTouchEvent方法的实现。
想看懂ViewGroup中dispatchTouchEvent方法的代码,必须要先大致了解ViewGroup的一个内部类,TouchTarget类
/* Describes a touched view and the ids of the pointers that it has captured.
*
* This code assumes that pointer ids are always in the range 0..31 such that
* it can use a bitfield to track which pointer ids are present.
* As it happens, the lower layers of the input dispatch pipeline also use the
* same trick so the assumption should be safe here...
*/
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
// The touched child view.
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
private TouchTarget() {
}
// 看到这个有没有很眼熟?是的Message里也有类似的实现,我们在之前介绍Message的文章里详细地分析过
public static TouchTarget obtain(View child, int pointerIdBits) {
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) { // 没有可以回收的目标,则new一个返回
target = new TouchTarget();
} else {
target = sRecycleBin; // 重用当前的sRecycleBin
sRecycleBin = target.next; // 更新sRecycleBin指向下一个
sRecycledCount--; // 重用了一个,可回收的减1
target.next = null; // 切断next指向
}
}
target.child = child; // 找到合适的target后,赋值
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() { // 基本是obtain的反向过程
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin; // next指向旧的可回收的头
sRecycleBin = this; // update旧的头指向this,表示它自己现在是可回收的target(第一个)
sRecycledCount += 1; // 多了一个可回收的
} else {
next = null; // 没有next了
}
child = null; // 清空child字段
}
}
}
源码分析:
TouchTarget类用来记录触摸对象,包括此次触摸到的view(child成员变量)和触摸的id(pointerIdBits成员变量)。TouchTarget自身维护着一个TouchTarget对象形成的链表,obtain方法会从这个链表头上取出一个TouchTarget来使用,如果此时链表没有TouchTarget可用,则新造一个给调用端;recycle方法会将不用的TouchTarget对象重置后添加到链表的头部,供以后使用。当多点触摸的时候,会有多个TouchTarget,这些TouchTarget都是通过obtian方法获取的,获取的TouchTarget对象会形成一个触摸链,这个触摸链的起点是mFirstTouchTarget。本文重点说明触摸的分发响应过程,简单起见,以单点触摸的情况来说明,单点触摸中只会有一个TouchTarget对象。
下面我们来看ViewGroup中dispatchTouchEvent的实现:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态
}
// Check for interception.
final boolean intercepted; // 检查是否要拦截
if (actionMasked == MotionEvent.ACTION_DOWN // down事件
|| mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法
intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false; // 不允许拦截的话,直接设为false
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
// 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
// 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一
// 开始的down事件就开始拦截),则接下来的所有事件
// 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣
intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了
// Update list of touch targets for pointer down, if needed.
// 是否拆分事件,3.0(包括)之后引入的,默认拆分
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件
if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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;
// 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 View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 从最后一个向第一个找
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue; // 不满足这2个条件直接跳过,看下一个child
}
// child view能receive touch事件而且touch坐标也在view边界内
newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget
if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指
// 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; // newTouchTarget已经有了,跳出for循环
}
resetCancelNextUpFlag(child);
// 将此事件交给child处理
// 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推
// 这样TouchTarget的链就形成了
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果处理掉了的话,将此child添加到touch链的头部
// 注意这个方法内部会更新 mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了
break; // 可以退出for循环了。。。
}
}
}
// 本次没找到newTouchTarget但之前的mFirstTouchTarget已经有了
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;
}
// while结束后,newTouchTarget指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 没有children处理则派发给自己处理
// No touch targets so treat this as an ordinary 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;
while (target != null) { // 遍历TouchTarget形成的链表
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true; // 已经处理过的不再让其处理事件
} else {
// 取消child标记
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件
// 如果cancelChild为true的话
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true; // TouchTarget链中任意一个处理了则设置handled为true
}
if (cancelChild) { // 如果是cancelChild的话,则回收此target节点
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) {
// 取消或up事件时resetTouchState
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; // 返回处理的结果
}
源码分析(以代码执行的顺序分析):
重要成员变量
// First touch target in the linked list of touch targets.
private TouchTarget mFirstTouchTarget;//ViewGroup的child消耗touch,此变量记录下消耗touch的view
protected int mGroupFlags;//各种flag值的集合,其中包含FLAG_DISALLOW_INTERCEPT
1.触摸以MotionEvent.ACTION_DOWN为开始,在触摸的开始,ViewGroup会清除所有的关于上次触摸的信息。
8行一般为true,13行判断如果是ACTION_DOWN,则调用cancelAndClearTouchTargets()和resetTouchState(),在这两个方法中分别mFirstTouchTarget=null和重置mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位。
代码如下:
/**
* Cancels and clears all touch targets.
*/
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();
}
}
}
/**
* Resets the cancel next up flag.
* Returns true if the flag was previously set.
*/
private static boolean resetCancelNextUpFlag(View view) {
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
5行如果mFirsetTouchTarget!=null,会走到19行调用clearTouchTargets(),clearTouchTargets()中将mFirstTouchTarget为起点的链表都清空,最终将mFirstTouchTarget=null.
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
FLAG_DISALLOW_INTERCEPT标志位,决定了ViewGroup是否允许拦截touch事件,往往通过child.getParent().requestDisallowInterceptTouchEvent(true)方法来设置,当传入true的话,child的所有父控件都不会拦截touch事件,这样保证了child肯定会收到此touch事件。
requestDisallowInterceptTouchEvent代码如下:
/**
* {@inheritDoc}
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
tip:我们自己做项目时经常会重写child的onTouchEvent方法,判断当情况A时调用child.getParent().requestDisallowInterceptTouchEvent(true),来让父控件不去拦截touch事件,当情况B时,调用child.getParent().requestDisallowInterceptTouchEvent(false),运许父控件拦截touch事件,如果此时父控件也需要拦截touch,那么父控件会拦截touch,child再无法收到touch事件,child的onTouchEvent方法也就再得不到执行。但是,当用户松开手机再按下,产生下一个触摸事件时,child的onTouchEvent又得到执行,就是因为父控件ViewGroup在dispatchTouchEvent方法中判断MotionEvent的action为ACTION_DOWN,重置了mGroupFlags标志位所致。
2.ViewGroup通过MotionEvent的action,mFirstTouchTarget,mGroupFlags中FLAG_DISALLOW_INTERCEPT,三个值共同判断是否要拦截此touch。
(不拦截touch,ViewGroup会根据触摸在哪个view上,将touch逐层分发到那个view去,即逐层调用view的dispatchTouchEvent方法,如果拦截touch,则不会向下分发,直接调用ViewGroup的onTouchEvent方法,之后便结束了此ViewGroup的dispatchTouchEvent方法)
dispatchTouchEvent代码片段:
22到40行代码判断ViewGroup是否拦截touch事件
mFirstTouchTarget记录的是ViewGroup中消耗了touch事件的child的引用;
当一个新的触摸事件到来,由于之前重置了ViewGroup中记录的上一个touch的信息,此时MotionEvent的action为ACTION_DOWN并且mFirstTouchTarget==null,mGroupFlags中FLAG_DISALLOW_INTERCEPT为false,所以,进入27行代码,即此处触摸是否拦截又ViewGroup的onInterceptTouch方法决定。
当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent!=null,即ViewGroup中被触摸到的子View消耗了此touch事件,此时是否拦截touch由mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位值和onInterceptTouch方法返回值共同决定。
当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent==null,即ViewGroup中被触摸到的子View没有消耗此touch事件,进入39行,ViewGroup拦截touch事件。
tips:当我们自定义的View重写onTouchEvent方法,在onTouchEvent方法的ACITON_DOWN中返回false,此View便再无法收到其后的touch事件,就是因为ViewGroup的子View没有消耗touch事件,mFirstTouchTarget==null,intercept=true,ViewGroup便会拦截其后的touch事件。
3.根据intercept的值,决定是否拦截touch事件,不拦截将touch传递到触摸到的View上去,当分发到的View消耗了此touch,mFirstTouchTarget会记下此View;分发到的View没有消耗此touch,mFirstTouchTarget一直为null。
51行if(!cancel&&!intercepted),默认cancel为false,当intercept为false,即不拦截时,代码会走到64行,childrenCount !=0,即ViewGroup有child,会走到73行,从后往前遍历ViewGroup的所有child,77,78行判断是否触摸在此child上,并且此child接受触摸事件。
tips:我们经常使用FrameLayout来做重叠的布局,当我们的触摸事件落在多个重叠view上时,这时候如果前边的view消耗了事件,后边的view就收不到触摸事件了;就是因为ViewGroup在分发touch的时候,是从后往前遍历的(view重叠时,就是从上向下),如果上面的view消耗了touch,后边的view自然就接收不到touch了。
84-92行主要是对多点触控进行处理,暂时不管,代码进入关键的96行,调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),此方法将dispatchTouchEvent方法中传入的MotionEvent的相对于ViewGroup的x,y坐标转化为相对于child的x,y坐标,然后,分发给此child。
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.
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()) {
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 {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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;
}
13行,如果传入的cancel==true或者event.getAction()==MotionEvent.ACTION_CANCEL,把event的action设置为ACTION_CANCEL。
15行,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();
super.dispatchTouchEvent()就是调用view的dispatchTouchEvent()方法,view的dispatchTouchEvent实现,其实就是调用自身的onTouchEvent(),即child==null,调用ViewGroup的onTouchEvent(),child!=null,将touch分发给传入的child。
20行,恢复event的action为原来的action;
38-57行,60-71行也是类似逻辑,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();
由dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)调用参数可知,最终执行了child.dispatchTouchEvent()将触摸事件分发给了子view。
下面为View的dispatchTouchEvent源码
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) { // 一般都成立
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED状态下尝试调用onTouch方法
return true; // 如果被onTouch处理了,则直接返回true
}
// 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候,
// onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发;另外还有个区别就是onTouch里可以
// 收到每次touch事件,而onClickListener只是在up事件到来时触发。
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false; // 上面的都没处理,则返回false
}
View的dispatchTouchEvent方法很简单,16-17行,就是判断有没有设置OnTouchListener,如果设置了,并且OnTouchListener.onTouch()返回了true,则dispatchTouchEvent直接返回true;如果没有设置OnTouchListener或者OnTouchListener.onTouch()返回false,则到23行,调用onTouchEvent(),将onTouchEvent()的返回值,作为dispatchTouchEvent的返回值。
继续说ViewGroup的dispatchTouchEvent过程,如果child消耗了此touch,则会进入96行的if语句块中,最终执行到第104行,调用addTouchTarget()方法,那么在此方法中都做了什么呢,我们来看看其源码:
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
看以上代码,我们知道此方法就是在拿消耗掉触摸的child生成一个TouchTarget,添加到touch链的头部,并且mFirstTouchTarget指向此TouchTarget。主要就是更新mFirstTouchTarget的引用指向,消耗了此touch的child。
继续分析ViewGroup的dispatchTouchEvent方法
前面所有的判断机会都要求action为ACTION_DOWN,126行后终于没有了这条判断,也就是处理所有action。
126行,如果此ViewGroup的child没有处理此touch,mFirstTouchTarget==null,调用dispatchTransformedTouchEvent(ev, canceled, null, idBitsToAssign); 由上面分析dispatchTransformedTouchEvent方法代码可知,第三个参数child为null,会调用super.dispatchTouchEvent(),即ViewGroup的onTouchEvent。
也就是如果ViewGroup的child不消耗此touch,则调用ViewGroup的onTouchEvent方法。
134行后代码,则代表ViewGroup中有child消耗了此touch,135行将mFirstTouchTarget赋值给target,因为进入此else语句mFirstTouchTarget肯定不为空,所以target不为空,进入136行while循环,138行的if判断,对应于上边96行的if括号内的代码,如果这个touch第一次进行分发,走入了96行的if括号,也就是此touch的ACTION_DOWN被子view消耗后,代码走到138行,直接handled=true,不再处理。如果不是第一次分发,49行newTouchTarget=null,MotionEvent的action不是ACTION_DOWN,不会进入51,52行if语句块;所以这里会走到else语句。142行就是判断是否取消的操作,或者本来没有拦截现在变为了拦截。这里我们不讨论取消的情况,只考虑拦截和不拦截的情况,即cancelChild的值去intercepted的值。如果不拦截,cancelChild==false,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将此touch直接分发给mFirstTouchTarget记录的child;如果拦截(ViewGroup由原来不拦截件变为拦截),cancelChild==true,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将ACTION_CANCEL分发给mFirstTouchTarget记录的child。
150行,如果cancelChild==true,predecessor会一直为null,因为158行有continue语句,161行predecessor=target这句永远得不到执行,最终会把mFirstTouchTarget置为null,这样会导致23行的if语句块再得不到执行,及onInterceptTouchEvent方法再得不到执行。如果cancelChild==false,则mFirstTouchTarget会保存不变,只是把此touch分发到mFirstTouchTarget记录的child上去。
tips:如果ViewGroup的子View消耗了touch的ACTION_DOWN,此touch的后续事件,不再重新分发,直接传递到消耗ACTION_DOWN的View上去。
如果ViewGroup一开始不拦截,在后续事件中变为拦截(在onInterceptTouchEvent方法中根据滑动动作,返回了true),原来消耗touch的子View会受到ACTION_CANCEL事件,并且ViewGroup的onInterceptTouchEvent方法在此次touch事件中再不会执行。
nnd,终于写完了,写了好几天,语言不太会组织,程序员通病吧,单点触控只有一个TouchTarget,多点触控按下几个手指这几个手指触点会形成一个TouchTarget的链,简单起见,多点触控就没讲,单点和多点事件分发处理的过程是完全一样的的。大伙看思路就好。如有理解不一致的地方,还望在评论区留言讨论。