Andoird事件分发主要看ViewGroup的dispatchTouchEvent方法,只要这个方法看透彻了,事件分发原理就明白了九成九。
DOWN事件的分发:
判断如果是Down事件,会清除一些状态,重置mGroupFlags跟mFirstTouchTarget
// 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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
Down事件继续往下走
intercepted意思是 是否拦截该事件,若为true,则事件不会分发给子View,在事件是down事件时,进来会先重置状态,所以一般情况下会进来if(!disallowIntercept)这个判断, onInterceptTouchEvent()不重写的话,一般是不会拦截的,它的返回值为false。
mFirstTouchTarget(触摸目标)是一个单链表结构,当有view消费了down事件时,它会记录赋值。这里初始化为null。
每次进来之后会对newTouchTarget跟alreadyDispatchedToNewTouchTarget这两个参数赋初值
// Check for interception 表示是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
(mGroupFlags & FLAG_DISALLOW_INTERCEPT)在这里的目的是检查
mGroupFlags 中是否设置了 FLAG_DISALLOW_INTERCEPT 标志位。
如果结果为0,则表示 mGroupFlags 中没有设置该标志位;
如果结果为 FLAG_DISALLOW_INTERCEPT 的值,
则表示 mGroupFlags 中设置了该标志位。
// 一般默认情况下, mGroupFlags & FLAG_DISALLOW_INTERCEPT != 0 disallowIntercept 为true
// 子View可以通过 getParent().requestDisallowInterceptTouchEvent,
// 通过修改mGroupFlags来修改父亲disallowIntercept的值
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 一般会进来
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;
}
// ...
// Check for cancelation.
// canceled这个参数一般为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
// ...
// 两个需要注意的参数
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
如果此时intercepted为false,即没有被拦截,再接着判断了一下down事件,开始对该ViewGroup中所有子View进行排序,然后进行遍历,找到满足条件的子view,然后执行dispatchTransformedTouchEvent方法,在该方法中,如果子view为ViewGroup则会继续重复之前的流程,这里也就是为什么事件分发为树状图结构!!!如果为View则会调用View的dispatchTouchEvent方法,在这方法中会执行onTouchEvent方法,如果onTouchEvent方法返回true,则dispatchTouchEvent方法就会返回true,则dispatchTransformedTouchEvent会返回true;
然后通过addTouchTarget方法为mFirstTouchTarget赋值(ps:这里mFirstTouchTarge != null 成立),结束遍历,其他子view就不管了
注意,在addTouchTarget方法执行为mFirstTouchTarget赋值之后,下一行alreadyDispatchedToNewTouchTarget也赋值为true,唯一变为true的地方
// canceled为false,intercepted为false的时候,进入这个if
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;
// down事件, 进入这个if
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// ...
final int childrenCount = mChildrenCount;
// 前面一开始newTouchTarget == null, childrenCount 子View数 进入这个if
// 这里面做什么? 子View排序,遍历, 寻找能够处理该事件的子View
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍历子View
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.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 该View是否能接受点击事件 ,触屏点是否在该View里面
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 前面的两个if通过了,子View来到这里, getTouchTarget返回null
// 为何?里面是遍历mFirstTouchTarget,而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);
// 划重点 dispatchTransformedTouchEvent ,重点参数 cancel false,child
// 这两个参数,决定了里面会执行子view的dispatchTouchEvent方法
// 若返回true,代表有子view处理了事件,为mFirstTouchTarget赋值,结束遍历
// 返回false ,继续遍历
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();
// mFirstTouchTarget的赋值处
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 注意这个变量, 后面判断会用到
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;
}
}
}
接下来分析下一段代码:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 这里其实是自己处理点击事件了,里面调用super.dispatchTouchEvent(event);
// ViewGroup继承自View
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { // mFirstTouchTarget!= null
// 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和null
final TouchTarget next = target.next;
// down事件进来, mFirstTouchTarget!=null时,到这里alreadyDispatchedToNewTouchTarget为true
// target == newTouchTarget亦成立, handled = true 事件消费
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// cancelChild 重要到一个参数,前面的条件可以忽略, intercepted拦截值可以决定cancelChild的值
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 又是执行dispatchTransformedTouchEvent, 这里是MOVE、UP事件是否会变成CANCEL事件的关键
// 主要看第二个参数是否为ture了
// DOWN事件走不到这个分支来
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.
// ...
return handled;
如果当前ViewGroup没有被子view所消费,就会走 mFirstTouchTarget == null流程自己处理这个事件;如果当前ViewGroup被子view所消费,就会走mFirstTouchTarget != null流程;
这里假设会某一个ViewGroup类型的child消费(因为在遍历的时候是层层递归的,总有一个ViewGroup为true),这里走mFirstTouchTarget != null流程,因为已经在addTouchTarget中赋值过了,所以alreadyDispatchedToNewTouchTarget为true,target == newTouchTarget ==mFirstTouchTarget ,然后 handled =true 并返回。
到此,Down事件就走完了
看看dispatchTransformedTouchEvent这个方法——分发或处理事件的地方**
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)
这个方法大致逻辑就是,当 cancel为true时,将事件(MOVE、UP)变成CANCEL事件往下传,然后再通过event.setAction(oldAction)还原,当cancel为false时,就是将当前事件往下传;
若传进来的child是null,就调用super.dispatchTouchEvent,这个方法是在ViewGroup里面,ViewGroup继承自View,其实就是View的dispatchTouchEvent—— onTouch——onTouchEvent这个处理流程了,自己去处理这个事件;若传进来的child不是null,那么就继续分发或处理。
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;
}
//...后面还有
MOVE事件的分发:
事件被View消费,执行Move事件的分发,会走 mFirstTouchTarge != null 分支,此时mGroupFlags没有设置FLAG_DISALLOW_INTERCEPT标志位,接着intercepted被赋值为 true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}
此时不会再去遍历子view
/// ...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) { // intercepted为true, 没进来
// ...
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)
{
//... 没进来 因为是Move事件
}
}
接着往下走,因为mFirstTouchTarget != null,所以会走【2】—— 【3】——【4】
if (mFirstTouchTarget == null) { // 【1】
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { // 【2】
// 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 flase
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else { // 【3】
// intercept拦截的时候, cancelChild 为true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) { // 【4】
handled = true;
}
if (cancelChild) { // 【5】
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
然后执行dispatchTransformedTouchEvent方法,cancelChild 为 true,会将MOVE跟UP事件转成CANCEL事件去进行分发,如果子View是ViewGroup类型的,就会再次执行一遍之前的流程继续进行MOVE和UP事件的分发,如果子View的类型为View,则会去View.dispatchTouchEvent方法,然后去执行该View的onTouch事件并将处理的结果进行返回
注:特殊情况,如果子View类型为ViewGroup,且没有child,则会执行子View的==【1】流程==,自己去处理事件,这是为什么呢?
这是因为该视图在进行Down事件分发的时候,没有遍历去匹配视图并addTouchTarget
接下来会走【5】流程,会执行这一句mFirstTouchTarget = next;(前面【2】处代码predecessor == null),next为null,也就是mFirstTouchTarget == null
if (cancelChild) {
if(predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
dispatchTouchEvent代码流程走完了。。。 但是,Move事件可不是只会触发一次,它是连续触发的,Move事件第二次进来会发生什么? 这个时候mFirstTouchTarget == null,不一样了。
if (mFirstTouchTarget == null) { // 【1】
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
自己去处理MOVE事件,后续的MOVE事件就被它抢走了
所以,就算是down事件被某一个view消费了,它后续也不一定能收到move序列事件,是有可能被它的父亲抢了去的,那么如果是Activity – GroupView – GroupView – View 单链表结构,后续的MOVE事件是如何分发给View的呢?