Android事件分发中的ViewGroup

一定要看https://blog.csdn.net/bendan50/article/details/85704645

本文是基于上一篇来的,准确来说是接着上一篇文章继续Android的事件分发笔记(结论+图+源码)

上篇的结尾说到,当ViewGroup不进行拦截时,会对子View进行遍历,现在就继续从这部分源码往下看。

// 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;
                        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;
                            }

                            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();
                                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);
                        }
                        

两行注释已经说明了代码主要意图:从前向后遍历子View,已便找到能够接收此事件的View。里面使用了一个For循环,使用前序遍历遍历子View。这也符合我们平常的习惯,因为一般来说我们对屏幕的触摸,肯定是希望最上层的View来响应的,而不是被覆盖这的底层的View来响应,否则这有悖于生活体验。当然,也可以自定义遍历顺序,因为有一个customOrder变量,其值取决于两个因素:

final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();

接着就是按照遍历顺序得到了这个子View,源码变量定义为:child  。

“如果有一个视图具有可访问性焦点,我们希望它首先获得事件;如果没有处理,我们将执行一个正常的分发。我们可能需要进行两次迭代,但在给定的时间范围内,这样更安全”这是找到子View后对第一个if语句的注释,显然,i = childrenCount - 1;的赋值会让循环重置。

第二个IF语句,是判断触摸点位置是否在子View的范围内或者子View是否在播放动画,如果均不符合则continue,表示这个子View不符合条件,开始遍历下一个子View。如果没有开始遍历下一个子View,那么说明目标子View找到了,开始下发事件。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
   // Child wants to receive touch within its bounds.
...
}

再浏览下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;
        }
    ...
    ...
}

下面的代码还有一部分,但关键的思路已经清晰。当child != null 时,会执行child.dispatchTouchEvent(event)方法,事件成功的传递到了子View处。返回值就是消费不消费了事件。

再回到ViewGroup的dispatchTouchEvent()方法,看后续的代码,只剩下一个: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(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

短短几行代码,想说明什么问题呢?其实就是mFirstTouchTarget变量,前面,我们提到该变量指向处理了事件的子View,而指向的赋值操作就在这。如果所有的子View都不能消耗事件,那么mFirstTouchTarget将为null

引用参考文献中的一张图:

参考:https://www.jianshu.com/p/8236278676fe

说明两点:

1、ViewGroup默认不拦截任何事件,所以事件能正常分发到子View处(如果子View符合条件的话),如果没有合适的子View或者子View不消耗ACTION_DOWN事件,那么接着事件会交由ViewGroup处理,并且同一事件序列之后的事件不会再分发给子View了。如果ViewGroup的onTouchEvent也返回false,即ViewGroup也不消耗事件的话,那么最后事件会交由Activity处理。即:逐层分发事件下去,如果都没有处理事件的View,那么事件会逐层向上返回。
2、如果某一个View拦截了事件,那么同一个事件序列的其他所有事件都会交由这个View处理,此时不再调用View(ViewGroup)的onIntercept()方法去询问是否要拦截了....。

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值