Android 事件分发dispatchTouchEvent源码分析

Andoird事件分发主要看ViewGroup的dispatchTouchEvent方法,只要这个方法看透彻了,事件分发原理就明白了九成九。

DOWN事件的分发:

判断如果是Down事件,会清除一些状态,重置mGroupFlagsmFirstTouchTarget

            // 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的呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想迷路的小男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值