Android touch事件传递

    Android的touch事件分发机制,涉及到每一层级的处理和传递,比较复杂,本文是在参考以下日志和Android源码的基础上总结的,在此表示感谢:

    http://blog.csdn.net/guolin_blog/article/details/9097463

    http://blog.csdn.net/sinyu890807/article/details/9153747

    1.touch事件传递过程

    touch事件经过Android内核层的处理,最终会传递到activity的dispatchTouchEvent方法,由此开始一层层往下传递。即touch事件是从顶层开始一级级往下传的,从Activity传到触摸到的viewgroup,再从viewgroup一层层往下传,直到最底层的view,传递到每一层的都是dispatchTouchEvent方法。

    更为关键的是,touch操作分为action_down以及后续的action_move、action_cancel或action_up。这一系列事件被作为一个整体,如果某一层的dispatchTouchEvent返回了false,则后续的move等事件则不会再传递到该层(具体见下文的源码分析)。

   2.view默认dispatchTouchEvent方法

    从1的分析可知,如果上层viewgroup均不消费touch事件,最终会调用子view的dispatchTouchEvent方法,view类中该方法源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

    从第九行代码可以看出,当该view存在onTouchListener时,dispatchTouchEvent会先触发该listener,并且当onTouchListener中的ontouch方法返回true时,dispatchTouchEvent直接返回true,表示该view消费了该事件。如果不存在onTouchListener或者ontouch方法返回false,会继续调用view中的onTouchEvent方法,如果该方法返回true,dispatchTouchEvent返回true,否则返回false。

    由此可以看出,在view中,onTouchListener的优先级高于onTouchEvent,如果onTouchListener消费了该事件,则不会去调用onTouchEvent。

    接下来我们看一下view默认的onTouchEvent实现:

public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));//view为disabled,但clickable时,仍返回true
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        。。。
                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();   //触发view的onclick事件
                                }
                            }
                        }

                       。。。
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    。。。
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    。。。
                    break;
            }
            return true;   //如果该view是clickable,必然会返回true
        }

        return false;
    }
    代码比较长,为了便于理解,我已经略去了一些。重点是该方法的返回值,从代码可以看出,只要该view是clickable,则该方法必然返回true,从而使dispatchTouchEvent方法返回true(前提是能走到该方法)。因此,如果是一个button控件,由于其默认是clickable的,所以即使未添加onclick事件,仍然会消费touch事件,而imageview由于默认非clickable,所以如果不做处理,touch事件在ontouch_down之后,由于 dispatchTouchEvent返回false,后续的move、up事件则不会传到该view。

    3.ViewGroup的dispatchTouchEvent方法

    ViewGroup中dispatchTouchEvent要相对复杂一些,其主要流程如下:

    判断是否允许拦截touch事件,如果允许,则调用onInterceptTouchEvent方法,如果onInterceptTouchEvent返回true,代表拦截该事件,dispatchTouchEvent直接返回,否则继续往下走;

    寻找包含touch的坐标点的子view(可能为view也可能为viewgroup),然后将touch事件传到该view的dispatchTouchEvent方法。如果某一个子view的dispatchTouchEvent返回true,则将该view标记为targetview,后续的move、up等事件会直接传到该view,如果未找到这样的子view,则调用该ViewGroup的super.dispatchTouchEvent方法,ViewGroup的父类即View,View的dispatchTouchEvent方法我们已经在上面分析过了,由此可见,如果子view未能消费该事件,则会去调用ViewGroup的ontouch或onTouchEvent方法。

    这里有一个需要注意的地方:onInterceptTouchEvent的调用时机。源码如下:

 if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    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;
            }
    前两行是说,如果该事件是 ACTION_DOWN 事件,或者已经找到了上面说的targetview。第三行是在判断是否允许拦截,该flag通过requestDisallowInterceptTouchEvent设置。通过前两行我们可以看出,在默认允许拦截的情况下,ACTION_DOWN事件是一定会走onInterceptTouchEvent的,并且如果未能找到targetview,在后续的move、up中,是不会走onInterceptTouchEvent。通过实验,我发现当ACTION_DOWN找到targetview后,如果在ACTION_MOVE中将该事件拦截,则后续的up事件不会去调onInterceptTouchEvent,猜测可能在viewgroup拦截事件后将targetview置空。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值