android中viewgroup的事件传递分析

作者: 的一幕 | 来源:发表于2019-07-25 20:09 被阅读14次

    在上一篇中我们分析了从view的dispatchTouchEventonTouchListeneronTouch回调到onTouchEventonClickLiseneronClickandroid中view事件传递,在后面遗留了两个问题,那就是在onTouchEvent中返回false的时候,只触发到action_down事件,以及在dispatchTouchEvent中返回false也是只触发到action_down事件,今天就带着这两个问题分析是如何会只执行到action_down事件。
    开篇还是用上面的例子,但是因为涉及到viewgroup因此在外层放一个父布局用作监听:

    image.png
    public class EventActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
    
        private static final String TAG = EventActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            View testView = findViewById(R.id.test_view);
            TestViewGroup testViewGroup = findViewById(R.id.test_viewgroup);
            testView.setOnClickListener(this);
            testView.setOnTouchListener(this);
            testViewGroup.setOnClickListener(this);
            testViewGroup.setOnTouchListener(this);
        }
    
        @Override
        public void onClick(View v) {
            Log.d(TAG, "onClick-----" + v.getClass().getSimpleName());
        }
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            String actionName = "";
            if (action == MotionEvent.ACTION_DOWN) {
                actionName = "action_down";
            } else if (action == MotionEvent.ACTION_MOVE) {
                actionName = "action_move";
            } else if (action == MotionEvent.ACTION_UP) {
                actionName = "action_up";
            }
            Log.d(TAG, "onTouch-----" + v.getClass().getSimpleName() + ";" + actionName);
            return false;
        }
    }
    

    外层用了个TestViewGroup:

    public class TestViewGroup extends RelativeLayout {
        private static final String TAG = TestViewGroup.class.getSimpleName();
    
        public TestViewGroup(Context context) {
            super(context);
        }
    
        public TestViewGroup(Context context, AttributeSet attrs) {
            super(context, attires
        }
    
        public TestViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            int action = event.getAction();
            String actionName = "";
            if (action == MotionEvent.ACTION_DOWN) {
                actionName = "action_down";
            } else if (action == MotionEvent.ACTION_MOVE) {
                actionName = "action_move";
            } else if (action == MotionEvent.ACTION_UP) {
                actionName = "action_up";
            }
            Log.d(TAG, "TestViewGroup dispatchTouchEvent-----" + actionName);
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            int action = event.getAction();
            String actionName = "";
            if (action == MotionEvent.ACTION_DOWN) {
                actionName = "action_down";
            } else if (action == MotionEvent.ACTION_MOVE) {
                actionName = "action_move";
            } else if (action == MotionEvent.ACTION_UP) {
                actionName = "action_up";
            }
            Log.d(TAG, "TestViewGroup onInterceptTouchEvent-----" + actionName);
            return super.onInterceptTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int action = event.getAction();
            String actionName = "";
            if (action == MotionEvent.ACTION_DOWN) {
                actionName = "action_down";
            } else if (action == MotionEvent.ACTION_MOVE) {
                actionName = "action_move";
            } else if (action == MotionEvent.ACTION_UP) {
                actionName = "action_up";
            }
            Log.d(TAG, "TestViewGroup onTouchEvent-----" + actionName);
            return super.onTouchEvent(event);
        }
    }
    

    大家都知道viewgroup的事件传递多了个onInterceptTouchEvent方法,专门用来控制要不要拦截onTouchEvent事件,所以这里也默认将该方法给重写了。下面来看下默认的点击事件日志,先在里面的testview上点击下:

    image.png
    整理的一张默认流程图.png
    下面如果再点击testview外面区域的话,看看日志又是如何:
    image.png
    此处可以看出来会触发到testviewgrouponTouchEvent事件以及它的onClick事件,是因为没找到里面子view触发的点击事件,因此会传给自己的onTouchEventonClick事件,而且在action_up中没有触发testviewgrouponInterceptTouchEvent方法,这些一系列问题还是要从源码去分析发生了什么,下面带着大家一步步分析源码,既然从日志上看是先调用了testviewgroup的dispatchTouchEvent方法,因此咋们从viewgroup的dispatchTouchEvent入手。

    viewgroup之dispatchTouchEvent

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //省略代码
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            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.
                //1.清除targetView
                cancelAndClearTouchTargets(ev);
                //2.重置touchState
                resetTouchState();
            }
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //3.mGroupFlags和FLAG_DISALLOW_INTERCEPT进行位与运算,mGroupFlags由于默认是0,因此在跟FLAG_DISALLOW_INTERCEPT进行位与的情况下肯定是等于0的,因此disallowIntercept肯定是false了
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //4.默认会走onInterceptTouchEvent方法
                    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;
            }
            // 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.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //5.如果上面不拦截在action_down的时候会走这里
            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.
                    removePointersFromTouchTargets(idBitsToAssign);
                    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.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //6.可以看到这里有个反向遍历,其实可以想到这里是后添加的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;
                            }
                            //7.如果view不可见,获取点击的区域不在子view里面,直接跳出该次循环
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //8.获取点击的view,这里其实获取到的newTouchTarget是空的
                            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);
                             //9.该处很关键,这里是传递到子view的dispatchTouchEvent事件的关键
                            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();
                                //10.该处是处理newTouchTarget和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.
            //12.该处很重要,看到没传给dispatchTransformedTouchEvent的child=null,这个是上一节view事件传递遗留的问题关键点
            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;
                    //11.这里由于上面alreadyDispatchedToNewTouchTarget=true以及target就是mFirstTouchTarget,上面也分析了mFirstTouchTarget和newTouchTarget指的是同一个变量
                    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;
                }
            }
            // Update list of touch targets for pointer up or cancel, if needed.
            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);
            }
        }
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
    

    在注释1的地方调用了cancelAndClearTouchTargets方法,从字面意思看是取消和清除了TouchTargets,看看该方法:

    /**
         * Cancels and clears all touch targets.
         */
        private void cancelAndClearTouchTargets(MotionEvent event) {
            if (mFirstTouchTarget != null) {
                boolean syntheticEvent = false;
                if (event == null) {
                    final long now = SystemClock.uptimeMillis();
                    event = MotionEvent.obtain(now, now,
                            MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                    event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                    syntheticEvent = true;
                }
    
                for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                    resetCancelNextUpFlag(target.child);
                    dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
                }
                clearTouchTargets();
    
                if (syntheticEvent) {
                    event.recycle();
                }
            }
        }
    

    上面调用了resetCancelNextUpFlag方法进行resetCancel标志,已近后面调用了clearTouchTargets

    /**
         * Clears all touch targets.
         */
        private void clearTouchTargets() {
            TouchTarget target = mFirstTouchTarget;
            if (target != null) {
                do {
                    TouchTarget next = target.next;
                    target.recycle();
                    target = next;
                } while (target != null);
                mFirstTouchTarget = null;
            }
        }
    

    看到了没,上面要做的就是不断的循环然target,最后将mFirstTouchTarget至为了null,后面要用到该变量。继续回到dispatchTouchEvent方法的注释2,调用了resetCancelNextUpFlag方法。在注释3的地方final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0这么一句,该句很关键,用mGroupFlags变量和FLAG_DISALLOW_INTERCEPT常量进行位与,FLAG_DISALLOW_INTERCEPT=0x80000,所以如果mGroupFlags是默认值那肯定进行位与之后必须=0,那么disallowIntercept为false,所以默认会走注释4的地方,会调用onInterceptTouchEvent方法,所以走不走onInterceptTouchEvent方法,可以通过控制mGroupFlags变量,在viewgroup源码中有直接设置mGroupFlags变量的方法:

     @Override
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
            if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
                // We're already in this state, assume our ancestors are too
                return;
            }
            //如果传进来的disallowIntercept=true,此时和FLAG_DISALLOW_INTERCEPT进行位或运算,那上面的方法中mGroupFlags和FLAG_DISALLOW_INTERCEPT进行位与肯定不等于0,disallowIntercept=true,就不会调用`onInterceptTouchEvent`方法
            if (disallowIntercept) {
                mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
            } else {
                mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
            }
    
            // Pass it up to our parent
            if (mParent != null) {
                mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
            }
        }
    

    从上面的注释来看,如果传进来的disallowIntercept=true,则不会走onInterceptTouchEvent,也就是不拦截,反之拦截。再回到注释4,咋们可以看看onInterceptTouchEvent方法内部的实现:

    public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                    && ev.getAction() == MotionEvent.ACTION_DOWN
                    && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                    && isOnScrollbarThumb(ev.getX(), ev.getY())) {
                return true;
            }
            return false;
        }
    

    可以看到默认是返回false,所以注释4的intercepted=false,接着到了注释5的地方了,接着走到了注释6的地方,通过反序遍历viewgroup中的子view,其实很好理解反序遍历,因为viewgroup中addview或在xml布局文件中后添加的view肯定在viewgroup的最上面,因此最先处理最上面的view的onTouch事件。在注释7我们可以看到调用了if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {分别判断是不是可见和是否点击了该view的区域:

     /**
         * Returns true if a child view can receive pointer events.
         * @hide
         */
        private static boolean canViewReceivePointerEvents(@NonNull View child) {
            //和VISIBILITY_MASK常量位与或者动画不为空
            return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                    || child.getAnimation() != null;
        }
    
    /**
         * Returns true if a child view contains the specified point when transformed
         * into its coordinate space.
         * Child must not be null.
         * @hide
         */
        protected boolean isTransformedTouchPointInView(float x, float y, View child,
                PointF outLocalPoint) {
            final float[] point = getTempPoint();
            point[0] = x;
            point[1] = y;
            //点击的点存储
            transformPointToViewLocal(point, child);
            //点击的点是不是在child上
            final boolean isInView = child.pointInView(point[0], point[1]);
            if (isInView && outLocalPoint != null) {
                outLocalPoint.set(point[0], point[1]);
            }
            return isInView;
        }
    

    所以如果不可见或者点击区域不在该view上直接continue该次循环,所以如果没有点击到viewgroup的子view身上是直接跳出该次遍历的,也就不会有后面的子view的dispatchTouchEventonTouchEvent事件。所以默认点击了子view是不会continue该次循环,紧接着到了注释8,该处调用了getTouchTarget方法:

     /**
         * Gets the touch target for specified child view.
         * Returns null if not found.
         */
        private TouchTarget getTouchTarget(@NonNull View child) {
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                if (target.child == child) {
                    return target;
                }
            }
            return null;
        }
    

    此处的mFirstTouchTarget在一开始的action_down的时候至为null了,所以此处返回的TouchTarget也是null,至少值action_down的时候是null。紧接着到了注释9的地方,可以看到此处调用了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();
            //默认不会走这里,cancel的时候才会走这里
            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;
            }
    
            // Calculate the number of pointers to deliver.
            final int oldPointerIdBits = event.getPointerIdBits();
            final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
            // If for some reason we ended up in an inconsistent state where it looks like we
            // might produce a motion event with no pointers in it, then drop the event.
            if (newPointerIdBits == 0) {
                return false;
            }
    
            // If the number of pointers is the same and we don't need to perform any fancy
            // irreversible transformations, then we can reuse the motion event for this
            // dispatch as long as we are careful to revert any changes we make.
            // Otherwise we need to make a copy.
            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);
            }
    
            // Perform any necessary transformations and dispatch.
            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());
                }
                //此处很关键的一个点,调用了child的dispatchTouchEvent方法
                handled = child.dispatchTouchEvent(transformedEvent);
            }
    
            // Done.
            transformedEvent.recycle();
            return handled;
        }
    

    该方法直接看最后调用了child.dispatchTouchEvent(transformedEvent),并且将child的dispatchTouchEvent返回值作为该方法的返回值,所以咱们可以看看该方法的返回值意义何在,继续回到注释9的位置,如果返回true,说明子view的dispatchTouchEvent返回true,在上一篇分析android中view事件传递,如果view的mOnTouchListener.onTouch方法返回true或者onTouchEvent返回true,或者不重写dispatchTouchEvent方法,直接返回true三种情况。继续看注释10处调用了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被赋值了,说明只有在dispatchTransformedTouchEvent方法返回true才能被赋值
            mFirstTouchTarget = target;
            return target;
        }
    

    紧接着将alreadyDispatchedToNewTouchTarget变量至为true,
    最后break循环了,此处才是真正找到了touch的子view了,所以没必要再去找了。到现在为止,newTouchTargetmFirstTouchTarget都不为空,并且两个是指同一个targetView。直接看注释11的位置,由于上面分析alreadyDispatchedToNewTouchTarget=true以及mFirstTouchTarget==newTouchTarget,因此直接进到if了handled = true;,走完了action_down后,我们知道mFirstTouchTargetnewTouchTarget都不为空,所以在action_moveaction_up的时候intercepted=false,还是会走dispatchTransformedTouchEvent方法,因此会执行子view的dispatchTouchEvent方法的action_move和action_down。所以到此默认情况都走完了一遍。
    回顾上一篇遗留的问题
    还记得上一篇在view的ontouchEvent中如果返回false是不是直接不走action_move和action_down呢,不熟悉的view的流程看下上一篇的讲解android中view事件传递,从上面viewgroup的dispatchTouchEvent方法可以知道,如果子view的ontouchEvent直接返回false,那么mFirstTouchTargetnewTouchTarget都为空,那么intercepted=true,所以不会走if (!canceled && !intercepted) {该处,直接走到了注释12处,调用了handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);此处的child传的是null,咋们可以看下dispatchTransformedTouchEvent如果传的是null会是咋样:

    image.png
    看到了没,直接不走child的dispatchTouchEvent方法,因此action_move和action_up都不会走child的dispatchTouchEventontouchEvent,同理如果child的dispatchTouchEvent方法直接返回false也是只走action_down事件。

    开篇遗留的问题:点击子view之外的区域,在action_up的时候不走viewgroup的onInterceptTouchEvent方法
    经过上面的分析,咋们知道如果点击了子view的话,mFirstTouchTarget变量才不会为空,因此if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {该if不成立,所以在action_up时不会调用viewgroup的onInterceptTouchEvent方法,包括action_move的时候也不会走。

    总结

    • 在action_down的时候,首先去判断viewgroup的onInterceptTouchEvent是不是拦截了,如果拦截的话intercepted=true,就会走handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);方法,此处传的child是null,因此直接走super.dispatchTouchEvent方法,不走child的dispatchTouchEvent方法
    • 如果onInterceptTouchEvent不拦截,那么在action_down的时候,去获取child.dispatchTouchEvent方法,如果返回true,那么mFirstTouchTargetnewTouchTarget都不为空,因此在action_move和action_up的时候会走child的dispatchTouchEventontouchEvent方法
    • 如果child的dispatchTouchEvent方法返回false或者child的ontouchEvent返回false,mFirstTouchTargetnewTouchTarget都为空,因此在action_moveaction_up的时候不走child的dispatchTouchEventontouchEvent方法。
    • 如果点击的是viewgroup,那么viewgroup的onInterceptTouchEvent的action_move和action_up都不会被执行。

    相关文章

      网友评论

        本文标题:android中viewgroup的事件传递分析

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