View Touch事件分发机制

前言

首先,此文章主要讲解View的Touch事件分发机制,旨在弄清楚事件分发的主要原理,因此,观其大略而不拘泥技术细节。

总览
Touch事件的分发,围绕着dispatchTouchEvent() 、onInterceptTouchEvent()、onTouchEvent()做文章,弄清楚三者的触发时机,也就弄清楚了机制。

dispatchTouchEvent();
onInterceptTouchEvent();
onTouchEvent();

入口
首先,一个UI的开始,是一个ViewGroup,因此,可以得知Touch事件先由ViewGroup接手,下面是ViewGroup的dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {

...... // 省略代码

     // 是否处理(消费)了此次事件
     boolean handled = false;
     if (onFilterTouchEventForSecurity(ev)) {

         .......//省略代码

        if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    // 是否拦截此次Touch事件,默认为false
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                // 走到这里表示没有可以被Touch的目标
                // 因此当作拦截
                intercepted = true;
          }

        ......// 省略代码

        // 没被取消且没被拦截
        if (!canceled && !intercepted) {

            ......// 省略代码
            //所有子View
            final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {

                ......// 省略代码

            // 通过dispatchTransformedTouchEvent调用View.disapatchEvent()
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                   mLastTouchDownTime = ev.getDownTime();
                   if (preorderedList != null) {
                   for (int j = 0; j < childrenCount; j++) {
                       if (children[childIndex] == mChildren[j]) {
                       mLastTouchDownIndex = j;
                       break;
                       }
                   }
                   } else {
                      mLastTouchDownIndex = childIndex;
                   }
                   mLastTouchDownX = ev.getX();
                   mLastTouchDownY = ev.getY();
                   newTouchTarget = addTouchTarget(child, idBitsToAssign);
                   alreadyDispatchedToNewTouchTarget = true;
                   break;
            }
            }
            ......// 省略代码
        }

    }
    return handled
}

源码的代码还是很长的,已经做了很多的省略截取了关键部分。在dispatchTouchEvent()里,用handled记录Touch事件是否被消费。首先,先通过onInterceptTouchEvent()判断此ViewGroup是否拦截事件,如果拦截,最终会返回true,如果ViewGroup上没有可以Touch的目标,就默认为拦截,这个应该比较好理解。如果不拦截并且事件没有被取消,会通过dispatchTransformedTouchEvent()将事件分发给子View。

事件拦截
onInterceptTouchEvent()负责被表明是否拦截Touch事件,下面是源码

    /**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
    */
    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;
    }

此函数的作用,注释说的很明白: 实现此方法中断Touch事件(默认为不中断,返回false). 不赘述

分发事件到子View
如果事件没有被ViewGroup消费,那么ViewGroup会通过dispatchTransformedTouchEvent()将Touch事件分发到子View里

    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();
        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;
        }

       ......// 省略

        return handled;
    }

dispatchTransformedTouchEvent()会根据child情况的不同分别调用super.dispatchTouchEvent(event)和child.dispatchTouchEvent(event),而ViewGroup继承子View,因此,自然会调用到View的dispatchTouchEvent()

View的dispatchTouchEvent()

   public boolean dispatchTouchEvent(MotionEvent event) {
        // 检查当前View是否可以获得焦点,由此可见,能获得焦点是能被Touch的前提
        if (event.isTargetAccessibilityFocus()) {
            // 无法获得焦点,无法消费此次事件
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        ...... // 省略

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }

            // ListenerInfo存有各种Listener信息,如OnClickListener,OnTouchListener,OnFocusChangeListener等等
            ListenerInfo li = mListenerInfo;
             // 调用 OnTouchListener.onTouch()
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    // 设置了OnTouchListener会优先调用,优先级比onTouchEvent高
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...... // 省略

        return result;
    }

当调用到dispatchTouchEvent()时,会先去看看当前View是不是可以获得焦点,在可以的情况下再进行下一步。如果当前View设置了OnTouchListener,会优先调用OnTouchListener.onTouch();没有设置OnTouchListener.onTouch或者OnTouchListener.onTouch.onTouch()返回false,则再去调用onTouchEvent()

消费事件onTouchEvent()

    public boolean onTouchEvent(MotionEvent event) {

        ...... // 省略

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:

                   ...... // 省略
                                   // 执行点击事件
                                    performClick();
                   ...... // 省略

            }

            return true;
        }

        return false;
    }

onTouchEvent()默认返回false,代表Touch事件没有被消费,可以根据需要重写onTouchEvent()。当然,就算再onTouchEvent()做了相应的处理,也是可以继续将事件传递下去,只要不要返回true就可以。 在onTouchEvent()里,还会根据手势的不同,执行不同的动作。

回顾
Touch事件从ViewGroup开始,通过dispatchTouchEvent()来分发事件。首先,会通过onInterceptTouchEvent()来判断是否要拦截此次事件,如果拦截,就代表ViewGroup会处理此次事件;反之,会逐一访问子View,子View再根据自己的onTouchEvent()来判断是否处理(消费)此次事件,如果子View设置了OnTouchListener并且OnTouchListener.onTouch()返回true,则不会调用到onTouchEvent(),认为消费了此次事件。一旦有一个子View消费了事件,Touch事件便不会再传递。流程如下图

这里写图片描述

而这样的传递机制,是一种责任链。 什么事责任链?简单来说,就说接到一个任务,先看看自己做不做,做了就算完成(被消费),否则交给下级去做,只要有其中一个下级做了,这件任务就算完成了。 重复这个过程,直到任务被完成(被消费)。或者遍历了完所有,并没有谁来完成这件任务。如图

这里写图片描述

当收到一个Touch事件时,如果自身不消费,会向下传递,当都没有被消费,逐层返回false,最后认为事件没有被消费;反之,如在图中节点4事件被消费,逐层返回true,代表事件被消费

总结
1、Touch事件的第一个接受者为ViewGroup
2、ViewGroup 可以对Touch事件进行拦截,如果不拦截则向子View分发
3、如果View设置了OnTouchListener,当接受到Touch事件时会调用OnTouchListener,且消费了此次事件,不会调用到onTouchEvent()
4、View通过onTouchEvent()返回了true,则认为消费了事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值