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