android中viewgroup的事件传递分析

在上一篇中我们分析了从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都不会被执行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容