重新认识Android事件分发机制(2)— 核心篇2

本文详细解析了Android中ViewGroup点击事件的分发流程,包括ViewGroup如何判断是否拦截事件,以及事件如何传递给子View的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇我们讲到Activity是怎样把点击事件传递到ViewGroup中的,这篇我们将继续讲解点击事件在ViewGroup中是怎样传递的。

由于Android 5.0之后ViewGroup的源码发生了变化(更加复杂),但原理是相同的。因此我们本着可阅读性和方便理解性,采用5.0之前的源码进行讲解。

ViewGroup的事件分发源码:

public boolean dispatchTouchEvent(MotionEvent ev) {

    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }

    if (disallowIntercept || !onInterceptTouchEvent(ev)) {
        ev.setAction(MotionEvent.ACTION_DOWN);
        final int scrolledXInt = (int) scrolledXFloat;
        final int scrolledYInt = (int) scrolledYFloat;
        final View[] children = mChildren;
        final int count = mChildrenCount;

        for (int i = count - 1; i >= 0; i--) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  || child.getAnimation() != null) {
                child.getHitRect(frame);

                if (frame.contains(scrolledXInt, scrolledYInt)) {
                    final float xc = scrolledXFloat - child.mLeft;
                    final float yc = scrolledYFloat - child.mTop;
                    ev.setLocation(xc, yc);
                    child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;

                    if (child.dispatchTouchEvent(ev))  {
                        mMotionTarget = child;
                        return true;
                    }
                }
            }
        }
    }

    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
            if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
     }
    final View target = mMotionTarget;

    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
         }

        return super.dispatchTouchEvent(ev);
    }
}

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

我们分两步去讲分发流程,然后再去处理疑惑点。

一、当ViewGroup不拦截点击事件时

1.我们首先看第二个if的disallowIntercept判断条件,这个条件是干嘛的呢?disallowIntercept = 是否禁用事件拦截的功能,默认条件是false。当我们的ViewGroup不拦截时,!onInterceptTouchEvent(ev)就会取反

走到if方法中

2.在这个if条件中,做了三件事,第一件是遍历ViweGroup的子View;第二件是找到被点击的View;第三是调用被点击View的dispatchTouchEvent方法,返回true。

3.至此ViewGroup的分发流程结束,点击事件被传递到子View中

二、当ViewGroup拦截事件

1.我们知道,当ViewGroup拦截事件后,ViewGroup会自己处理事件。从源码上看,肯定走了if(target == null)这个条件,然后调用super.dispatchTouchEvent方法。

流程图大概是这个样子

看完这个流程图,你是不是有几个疑问?

1.如果ViewGroup不拦截,ViewGroup遍历子View时,是如何找到被点击的子View的?

2.当ViewGroup拦截时,为什么if(target == null)时去调用super.dispatchTouchEvent,target是什么?

3.为什么要调用父类View的dispatchTouchEvent?

下面我们一个问题一个问题来解决。

1.如何找到被点击的子View?

①我们首先看ViewGroup遍历子View时的第一个if条件判断(child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)

这个条件判断的意思是,判断当前子View是否可点击或者正在播放动画。如果可以点击或者没有播放动画,那么这时候这个子View就算初步如选了。

②拿到初步如选的子View的点击范围,即点击域

③通过获取按下点的坐标系(X,Y)进行判断 = "frame.contains(scrolledXInt, scrolledYInt)"。如果按下点坐标在这个子View的点击范围内,说明就是当前这个子View的点击事件

2.target是什么?为什么target == null 时去调用super.dispatchTouchEvent?

从上面我们可以看出,target其实就是个View,是什么View呢?其实就是ViewGroup的子View。

我们要搞懂这个问题,首先需要看一个变量mMotionTarget,只有了解它,才能明白整个事件流程。

mMotionTarget有什么用?

其实它的作用是记录一个View及其对应分发的触控点列表,且可以通过next与其他实例形成链表。

它把消耗事件的View以链表方式保存,且记录各个View对应的触控点列表,以实现后续的事件派分处理。

是不是还是有点懵?没事,再简单点说它相当于一个可以被触摸的对象,它中记录了接受触摸事件的View。

当我们首次down时,mMotionTarget被置成null.

if (action == MotionEvent.ACTION_DOWN) {

    if (mMotionTarget != null) {

        mMotionTarget = null;

    }

}

如果ViewGroup不拦截,在View Group遍历的时候接受点击事件的子View就被它记录了

if (child.dispatchTouchEvent(ev)) {

    mMotionTarget = child;

    return true;

}

如果拦截,这时候mMotionTarget就没有记录任何子View,这时候程序会把它赋值给Target,

final View target = mMotionTarget;

所以这时候target == null,自然就走了super.dispatchTouchEvent方法。

3.问什么要调用父类的dispatchTouchEvent方法?

这是因为当不存在消耗ACTION_DOWN事件的目标控件时,后续事件的拦截标记intercepted将会越过用户处理表现为true,可以理解为ViewGroup退化成View,事件处理将交给super.dispatchTouchEvent进行

好了,以上就是整个ViewGroup的分发流程,由于篇幅过长内容过多,看起来会迷糊,不好消化。所以View的分发流程,我将会在下篇讲述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值