Android事件处理(一)——ViewGroup的dispatchTouchEvent 函数源码详解

Android事件处理(一)——ViewGroup的dispatchTouchEvent 函数源码详解

作者: 林子木

博客地址:http://blog.csdn.net/wolinxuebin


文章意图:

主要是想一自己阅读代码后的一些小收获分享给大家。让大家更加深入的了解Android的事件分发这块的内容。

文章主要内容:

本文将在第一章通过自己的语言,简单介绍dispatchTouchEvent,并将其中的一些关键点直接提炼出来,方便那些不想阅读源代码的同学把握住其中的关键点。

在第二章将放上源码,其中包含了我阅读过程中的26处注释。

第一章、dispatchTouchEvent关键点提炼

ViewGroup的dispatchTouchEvent是对View的dispatchTouchEvent函数的重新,两者具有很大的区别。如下表:

View的dispatchTouchEvent函数ViewGroup的dispatchTouchEvent函数
1)是将Event派发给自己的onTouchEvent函数处理。

1)将Event派发给子View

2)只有在子View没有相应该事件或ViewGroup拦截了该事件的时候,才通过View的dispatchTouchEvent将事件派发给自身。

有上面两个表,应该大致了解了ViewGroup的dispatchTouchEvent大致讲上面了吧。那现在具体讲讲做了哪些事:
Step 1:  如果收到的ACTION_DOWN 那么清除所有和时间相关的状态 
Step 2:  判断是否拦截事件

1) 如果在子view中调用了getParent().requestDisallowInterceptTouchEvent(true)那么将不会拦截该事件。
问:有什么用呢?
答:在listView或其他srcollView都会在onInterceptTouchEvent进行拦截判断,只要距离或者速度达到一定要求,就会拦截该事件。那就做不到一view在listView中上下滑动。那么如何才能实现这个需求?那就需要使用getParent().requestDisallowInterceptTouchEvent(true)不要拦截该事件,将事件传递下去。
2)onInterceptTouchEvent() 函数
这是一个很重要的函数,是在当前的事件达到一定要求之后才进行拦截处理。有了这个,listView才能实现item可点击,而item上下滑动。

Step 3: 如果事件是ACTION_DOWN ,并且没有被拦截,将会执行向子View的派发处理。

1)派发顺序

5.0之前,基本按照子View被添加的顺序

5.0之后,还需要考虑Z轴、Drawing顺序、添加的顺序

重点关注Z轴顺序,值最大的将会最优先,这是得到所有的子view,那么如何判断是否点击到子View区域内呢?

2)判断子View是否可见或者存在动画,否则不处理

3)事件是否点击到子View的区域内

    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        //[lxb] 如果view做了相应的变换,那么将point也做相应的变换
        transformPointToViewLocal(point, child);
        //[lxb] 直接将pointInView函数拿出
        //  return LocalX >= 0 && localX < (mRight - mLeft)
        //        && localY >= 0 && localY < (mBottom - mTop);
        // 经过变换之后的point,则已经将左上角作为原点,所以只要判断是否在这个矩形中就可以了
        // 所以这就是为什么如TranslateAnimation的原始点击点还是在最开始的地方
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
这也就是为什么补间动画尽管显示的View已经移动了另一个地方,但是点击区域还是在最原始的地方。

4)派发该事件给子View,看子View是否要处理

注意,parent将event该子view的时候做了相应的偏移处理,所以子view中的event.getX() 和 parent中的不一样。

具体如下:

<span style="font-size:12px;"><span style="white-space: pre;">	</span>//[lxb] 将event做相应的偏移之后,然后传递给相应的子view
<span style="white-space: pre;">	</span>final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        event.offsetLocation(offsetX, offsetY);

        handled = child.dispatchTouchEvent(event);</span>

5)将相应了该事件的子view加入到mFirstTouchTarget的单链表中(接下来的事件都会直接派发给其中的view)

Step 4: 如果没有一个子View相应Action_DOWN 怎么办?

那么直接将事件交给父类View的dispatchTouchEvent,再根据条件派发给自己。

Step 5: 其他事件如 ACTION_MOVE, ACTION_UP, ACTION_CANCEL则直接照着mFirstTouchTarget链表派发就行。

最后:

讲讲拦截,如果ViewGrop 拦截该事件,那么将立马给之前相应事件的子View派发一个ACTION_CANCEL事件。

另外,还需要注意的是,如果ViewGrop 拦截该事件,并不会立马将该事件给自己的onTouchEvent,只有等下次事件过来才可能,所以在onInterceptTouchEvent中需要对ACTION_UP和ACTION_CANCEL也做相应处理。

第二章、dispatchTouchEvent源码及分析

<span style="font-family:Microsoft YaHei;"> public boolean dispatchTouchEvent(MotionEvent ev) {
        //[lxb #1] 调试使用,请忽略
        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.
        //[lxb #2] [辅助功能] 事件将会第一个派发给开启了accessibility focused的view
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        //[lxb #3] 表示窗口是否为模糊窗口(FILTER_TOUCHES_WHEN_OBSCURED),如果是窗口,则表示不希望处理改事件。(如dialog后的窗口)
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            //[lxb #4] 过滤字段的最后8bit,也就是指只关心是ACTION_DOWN、ACTION_UP等事件,而不关心是哪个手指引起的。
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 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.
                //[lxb #5] 初始化相关状态
                //[lxb #5] (01) 清空mFirstTouchTarget链表,并设置mFirstTouchTarget为null。mFirstTouchTarget是"接受触摸事件的View"所组成的单链表
                //[lxb #5] (02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT标记,如果设置了FLAG_DISALLOW_INTERCEPT,ViewGroup对触摸事件进行拦截。
                //[lxb #5] (03) 清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN标记,作用是将下一个时间变我Cancel
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //[lxb #6] 如果为DOWN事件,或者mFirstTouchTarget为null(那么事件直接给到自己),就没必要执行拦截。
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //[lxb #7] 查看是否设置了,禁止拦截的标记
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //[lxb #8] ViewGroup的onInterceptTouchEvent不执行拦截,除非子类重写了该方法(如listview)
                    intercepted = onInterceptTouchEvent(ev);
                    //[lxb #9] 仅仅是避免action被篡改过。
                    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;
            }

            // 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.
            //[lxb #10] 查看时候被标记了PFLAG_CANCEL_NEXT_UP_EVENT 或者 当前是一个Cancel事件
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            //[lxb #11] 比如我们多个手指放到了屏幕上,是否要将第二个手指的事件下面下去
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity 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;

                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
                    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.
                    //[lxb #12] 清除Targets中相应的pointer ids 
                    removePointersFromTouchTargets(idBitsToAssign);

                    //[lxb #13] 遍历所有的child,将事件派发下去
                    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.
                        //[lxb #13] 以 1)Z轴(5.0系统引入) 2)draw的顺序 进行排序
                        final ArrayList<View> preorderedList = buildOrderedChildList();

                        //[lxb #14] 可以理解为,是否按照draw的顺序(因为,buildOrderedChildList在都没有设置Z的情况下返回null)
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //[lxb #15] 这里两端代码,简单的理解根据不同的排列选项(1、view添加到 2、view的draw顺序 3、viewZ 轴顺序)
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(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.
                            //[lxb #16] 如果存在开启了AccessibilityFocus 的view
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                //[lxb #17] 如果正是当前的childView开启了AccessibilityFocus,直接将i指向最后一个元素
                                //[lxb #17] 和 break的区别是,还将执行后面的代码,但是不会再进行循环了
                                i = childrenCount - 1;
                            }
                            //[lxb #18] canViewReceivePointerEvents 判断child是否为visiable 或者 是否有动画
                            //[lxb #18] isTransformedTouchPointInView 判断x, y是否在view的区域内(如果是执行了补间动画 则x,y会通过获取的matrix变换值
                            //[lxb #18] 换算当相应的区域,这也是为什么补间动画的触发区域不随着动画而改变)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //[lxb #19] getTouchTarget 查找child是否已经记录在mFirstTouchTarget这个单链表中
                            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);
                            //[lxb #20] 简单的理解,dispatchTransformedTouchEvent就是将相应的事件传递下去
                            //[lxb #20] 不过需要注意一点的就是,event被传递给child的时候将会做相应偏移,如下
                            //[lxb #20] final float offsetX = mScrollX - child.mLeft;
                            //[lxb #20] final float offsetY = mScrollY - child.mTop;
                            //[lxb #20] event.offsetLocation(offsetX, offsetY);
                            //[lxb #20] 为什么要做偏移呢? 因为event的getX得到的值是,childView到parentView边境的距离,是一个相对值
                            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
                                    //[lxb #21] 找到childIndex所代表的child的最原始的index【?】看代码,children和mChildren指向同一链表
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //[lxb #22] 将相应该事件的child包装成一个Target,添加到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();
                    }

                    //[lxb #22] 如果没有child相应该事件,则将此事件交给最近加入的target
                    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;
                    }
                }
            }

            //[lxb #23] mFirstTouchTarget == null 表示,没有能相应该事件的child,那么就调用父类(也就是View)的dispatchTouchEvent
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 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) {
                    final TouchTarget next = target.next;
                    //[lxb #24] 表示在Down事件处理中,已经将这个事件交给newTouchTarget处理过了,就不重复处理了
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //[lxb #25] 再次判定是否需要cancel(被标记为cancel 或者 事件被拦截)
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        //[lxb #26] 问:难道看到这里你们会不会产生一个疑问,如果parent在ACTION_MOVE过程中拦截了该事件,哪里hi处理呢?
                        //[lxb #26] 答:如果拦截了该事件,还是需要自身 dispatchTransformedTouchEvent 函数将事件交个自己的onTouchEvent
                        //[lxb #26]     此外dispatchTransformedTouchEvent完成上述操作需要一个条件,也就是child形参数为null
                        //[lxb #26] 问:那么怎么为null呢?
                        //[lxb #26] 答:往上面看10几行,不就是了吗?
                        //[lxb #26] 那么如何达到,其实条件就是 mFirstTouchTarget == null, 请看下面的分析
                        //[lxb #26]     如果intercepted == true的情况下, cancelChild == true, predecessor == null
                        //[lxb #26] 从而使得mFirstTouchTarget 一直 -> next,当target遍历到最后的时候,next == null,从而使得mFirstTouchTarget == null。
                        //[lxb #26] 问: 稍等,这里仅仅做了将mFirstTouchTarget 设置了为null,那么如何派发给自己的onTouchEvent呢?
                        //[lxb #26] 这个只能等下一个事件过来了
                        //[lxb #26] 结论【事件拦截,拦截了该事件,并没有将本次这个事件传递给自身的onTouchEvent,而需要等到下次】
                        //[lxb #26] 问:如何验证
                        //[lxb #26] 答:重新FrameLayout的 onInterceptTouchEvent 和 onTouchEvent 将相应的event.getEventTime打印出来,
                        //[lxb #26]     将会发现拦截的事件和传递到onTouchEvent的时间不是一个时间。
                        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.
            //[lxb #27] cancel ACTION_UP  ACTION_HOVER_MOVE(表示鼠标滑动)等,清理状态
            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);
            }
        }

        //[lxb #28] 调试使用,可以忽略 
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }</span>


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值