美文网首页实践知识点
android事件分发(2):ViewGroup

android事件分发(2):ViewGroup

作者: 众少成多积小致巨 | 来源:发表于2019-11-17 17:29 被阅读0次

    请码农们先看完这篇文章android事件分发(1):view

    简述

    ViewGroup是一种view,不仅具有view的功能,还是一种容器,可以容纳其它的view;因此它的事件分发流程包含了子view的事件追踪和自身事件的处理;ViewGroup事件方法 重写了dispatchTouchEvent,新增了onInterceptTouchEvent方法

    那么疑问来了,dispatchTouchEvent如何区分本身事件和子view事件的

    dispatchTouchEvent 方法分析

    1. 首先也是进行事件安全检测,安全就留继续处理,否则不处理
           if (onFilterTouchEventForSecurity(ev)) {
                ...........
            }
    
            if (!handled && mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
            }
    
    1. cancelAndClearTouchTargets 分发down事件给子view;并重置状态状态
              if (actionMasked == MotionEvent.ACTION_DOWN) {
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    

    cancelAndClearTouchTargets方法

                for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                    resetCancelNextUpFlag(target.child);
                    dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
                }
                clearTouchTargets(); // 重置子view数据
    

    dispatchTransformedTouchEvent 方法

    • 首先进行判断是否cancel事件,如果是,则进行分发
    • 对位置进行处理,得到屏幕上正确位置,然后进行分发
      分发规则: 根据传入view参数是否为空,来分发,不为空分发给view,否则自身父对象进行处理
     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;
            }
    
            final int oldPointerIdBits = event.getPointerIdBits();
            final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
            if (newPointerIdBits == 0) {
                return false;
            }
            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);
            }
            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);
            }
            transformedEvent.recycle();
    
    1. 判断是否事件部分发给子view;
    • mFirstTouchTarget 子view数据链表,在down事件中重置
    • onInterceptTouchEvent 容器是否拦截事件,不分发给子view
    • mGroupFlags,子view 调用容器requestDisallowInterceptTouchEvent方法进行设置;这个状态每次在down事件中重置
                 final boolean intercepted;
                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;
                    }
                } else {
                    intercepted = true;
                }
    
    1. 查找子view,获取 mFirstTouchTarget 数据,这个数据的规则,我也没有仔细看,自己研究吧少年
    • 如果down事件被拦截时, mFirstTouchTarget 一直为空,也就不会执行此不走
                 if (!canceled && !intercepted) {
                    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;
                        removePointersFromTouchTargets(idBitsToAssign);
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            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 (!child.canReceivePointerEvents()
                                        || !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;
                                }
                                ev.setTargetAccessibilityFocus(false);
                            }
                            if (preorderedList != null) preorderedList.clear();
                        }
    
                        if (newTouchTarget == null && mFirstTouchTarget != null) {
                            newTouchTarget = mFirstTouchTarget;
                            while (newTouchTarget.next != null) {
                                newTouchTarget = newTouchTarget.next;
                            }
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                        }
                    }
                }
    
    1. 根据传入mFirstTouchTarget 是否为空,来分发,不为空则寻找合适的子view进行分发,否则自身父对象进行处理
                if (mFirstTouchTarget == null) {
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {
                    TouchTarget predecessor = null;
                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;
                            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;
                    }
                }
    

    总结

    • ViewGroup事件分调用分发给自身,还是子view,根据mFirstTouchTarget 是否为空来处理
    • mFirstTouchTarget 是否为空,由三个因素影响
      1.是否容器的requestDisallowInterceptTouchEvent不允许容器进行拦截
      2.容器的onInterceptTouchEvent 在down的时候是否拦截事件
      3.点击区域是否有合适的子view进行处理
    • onInterceptTouchEvent 如果在down事件中进行拦截,则后续不会接收到事件

    相关文章

      网友评论

        本文标题:android事件分发(2):ViewGroup

        本文链接:https://www.haomeiwen.com/subject/izwpictx.html