Android开发-Touch事件分发响应机制

(1)概述
Android中的TouchEvent通常包含三个动作,ACTION_DOWN,ACTION_MOVE与ACTION_UP。发出去的顺序是DOWN->MOVE->UP (注意MOVE事件是否能够被触发取决于操作手势是否包含了移动的动作。)

消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View
消息响应流程,从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity

public boolean dispatchTouchEvent(MotionEvent ev);

事件分发处理函数,通常会在Activity层根据UI的显示情况,把事件传递给相应的ViewGroup。下面的演示代码中,为了方便模拟,会直接return true,解说中称之为“丢弃”。(因为事件实际上没有传递给任何组件,没有被消费,而且是主动的行为,故称之为丢弃)

public boolean onInterceptTouchEvent(MotionEvent ev);

对分发的事件进行拦截,注意拦截ACION_DOWN与其他ACTION的差异。
第1种情况:如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE与UP都能够下发。如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL。
第2种情况:如果ACITON_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE与UP都无法向下派发了,在Activity层就终止了传递。

public boolean onTouchEvent(MotionEvent ev);

响应处理函数,如果有设置对应listener的话,这里还会与onTouch,onClick,onLongClick有关联。具体执行顺序是onTouch()->onTouchEvent()->onClick()->onLongClick()。是否能够顺序执行,取决于每个方法的返回值是true还是false。具体这里不展开说。

强关注点:dispatch与intercept的差异,ACTION_DOWN与其他ACITON会对寻找target组件带来差异,而是否寻找到Target组件对整个流程有着重大的的影响。

(2)dispatchTouchEvent()的源码解读

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    boolean handled = false;
    ......
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    // 1)处理初始的ACTION_DOWN
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 把ACTION_DOWN作为一个Touch手势的始点,清除之前的手势状态。
        cancelAndClearTouchTargets(ev); //清除前一个手势,*关键操作:mFirstTouchTarget重置为null*
        resetTouchState(); //重置Touch状态标识
    }

    // 2)检查是否会被拦截
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        // 是ACTION_DOWN的事件,或者mFirstTouchTarget不为null(已经找到能够接收touch事件的目标组件)
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        // 判断禁止拦截的FLAG,因为requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法可以禁止执行是否需要拦截的判断
        if (!disallowIntercept) {
            // 禁止拦截的FLAG为false,说明可以执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法
            intercepted = onInterceptTouchEvent(ev); // 此方法默认返回false,如果想修改默认的行为,需要override此方法,修改返回值。
            ev.setAction(action);
        } else {
            // 禁止拦截的FLAG为ture,说明没有必要去执行是否需要拦截了,这个事件是无法拦截的,能够顺利通过,所以设置拦截变量为false
            intercepted = false;
        }
    } else {
        // 当不是ACTION_DOWN事件并且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操作。
        intercepted = true;
    }
    // 通过前面的逻辑处理,得到了是否需要进行拦截的变量值

    final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        // 不是ACTION_CANCEL并且拦截变量为false
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 在ACTION_DOWN时去寻找这次DOWN事件新出现的TouchTarget
            final int actionIndex = ev.getActionIndex(); // always 0 for down

            .....

            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                // 根据触摸的坐标寻找能够接收这个事件的子组件
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);

                final View[] children = mChildren;
                // 逆序遍历所有子组件
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = i;
                    final View child = children[childIndex];
                    // 寻找可接收这个事件并且组件区域内包含点击坐标的子View
                    if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                        continue;
                    }

                    newTouchTarget = getTouchTarget(child); // 找到了符合条件的子组件,赋值给newTouchTarget

                    ......

                    // 把ACTION_DOWN事件传递给子组件进行处理
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        // 如果此子ViewGroup消费了这个touch事件
                        mLastTouchDownTime = ev.getDownTime();
                        mLastTouchDownIndex = childIndex;
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        // 则为mFirstTouchTarget赋值为newTouchTarget,此子组件成为新的touch事件的起点
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
            ......
        }
    }

    // 经过前面的ACTION_DOWN的处理,有两种情况。
    if (mFirstTouchTarget == null) {
        // 情况1:(mFirstTouchTarget为null) 没有找到能够消费touch事件的子组件或者是touch事件被拦截了,
        // 那么在ViewGroup的dispatchTransformedTouchEvent方法里面,处理Touch事件则和普通View一样,
        // 自己无法消费,调用super.dispatchOnTouchEvent()把事件回递给父ViewGroup进行处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else {
        // 情况2:(mFirstTouchTarget!=null) 找到了能够消费touch事件的子组件,那么后续的touch事件都可以传递到子View
        TouchTarget target = mFirstTouchTarget;
        // (这里为了理解简单,省略了一个Target List的概念,有需要的同学再查看源码)
        while (target != null) {
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                // 如果前面利用ACTION_DOWN事件寻找符合接收条件的子组件的同时消费掉了ACTION_DOWN事件,这里直接返回true
                handled = true;
            } else {
                final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                // 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理(注意这里的非ACTION_DOWN事件已经不需要再判断是否拦截)
                if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                    // 如果target子组件进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件
                    // 条件是:如果ACTION_DOWN时没有被拦截,而后面的touch事件被拦截,则需要发送ACTION_CANCEL给target子组件
                    handled = true;
                }
                ......
            }
        }
    }

    if (canceled || actionMasked == MotionEvent.ACTION_UP) {
        // 如果是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都无法派发给子View
        resetTouchState();
    }
    ......

    return handled;
}

(3)dispatchTouchEvent()流程图

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值