震惊,View事件分发机制竟然是这样的

2017-03-16  本文已影响0人  一只小松

事件类型MotionEvent

MotionEvent的事件有三种类型:

当你点击一次屏幕的时候,整个过程中包含了一个ACTION_DOWN开始事件和多个的ACTION_MOVE以及一个ACTION_UP终止事件。当然如果没有在屏幕上滑动的也就没有ACTION_MOVE事件啦。

事件分发机制

通常我们的点击事件传递过程是Activity->Window->DecorView(View的事件分发机制),接下来具体介绍下这三者的事件传递过程:

1. Activity#dispatchTouchEvent的过程

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();// 该方法默认没有实现内容,子类Activity可在ACTION_DOWN发生时做特定互动
        }
        if (getWindow().superDispatchTouchEvent(ev)) {// 交由Window分发处理
            return true;
        }
        return onTouchEvent(ev);// 整个View树的onTouchEvent都返回false(不消费事件),仍然由Activity自身处理
    }

dispatchTouchEvent是系统事件传递地开端,是Window.Callback的一个重要回调方法,系统将屏幕点击事件传递于此。

2. PhoneWindow#superDispatchTouchEvent的过程

Window是一个抽象类,superDispatchTouchEvent也是个抽象方法,PhoneWindow是其唯一实现类。

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);// mDecor就是DecorView,即getWindow.getDecorView()返回的那个View
    }

3. 顶级View对事件的分发过程

我们先了解下几个会在View事件分发中使用到的方法:

以上三个方法的返回结果:

一段伪代码表示以上三个方法的关系:

public boolean dispatchTouchEvent(MotionEvent ev){
        boolean consume = false;
        if (onInterceptTouchEvent(ev)){
                consume = onTouchEvent(ev);
        }else{
                consume = child.dispatchTouchEvent(ev);
        }
        return consume;
}

那么可能有同学会问,不是还有onTouchListener和onClickListener吗?
这三者的优先级是onTouchListener > onTouchEvent > onClickListener,我们可以从View源码来验证。

public boolean dispatchTouchEvent(MotionEvent event) {
        ....
        boolean result = false;
        ....
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {// OnTouchListener优先调用(可用状态下)
                result = true;
            }

            if (!result && onTouchEvent(event)) {// 若OnTouchListener返回true,则onTouchEvent被屏蔽
                result = true;
            }
        }

        ....
        return result;
    }

onTouchListener若存在,onTouch方法返回true则会屏蔽掉onTouchEvent。

现在来看ViewGroup#dispatchTouchEvent,分段来说明:
1) requestDisallowInterceptTouchEvent设置FLAG_DISALLOW_INTERCEPT状态后,将使ViewGroup无法拦截除ACTION_DOWN以外的其他点击事件。换言之,尽管子元素有优先禁用父容器的拦截功能,但是对于ACTION_DOWN事件是个特例;

            // 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.
                cancelAndClearTouchTargets(ev);
                resetTouchState();// ACTION_DOWN事件重置了FLAG_DISALLOW_INTERCEPT标记位
            }

2) 这部分代码描述当前ViewGroup是否拦截点击事件的这个逻辑

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {// ACTION_DOWN动作和子元素是否成功处理(null表示没有子元素处理)
                // 若是ACTION_DOWN事件,FLAG_DISALLOW_INTERCEPT标记位会被清除,disallowIntercept为false
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {// 若子元素未禁止父容器的拦截功能(ACTION_DOWN肯定会执行)
                    intercepted = onInterceptTouchEvent(ev);// ViewGroup自己决定是否拦截
                    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. 即没有子view处理时(mFirstTouchTarget 若不为空说明子元素处理成功),事件由当前ViewGroup拦截,不再调用onInterceptTouchEvent来决定是否拦截
                intercepted = true;
            }

由上源码分析,我们可得出结论:

  1. ViewGroup决定拦截事件,那后续事件将默认交由处理,不需要再调用onInterceptTouchEvent
  2. FLAG_DISALLOW_INTERCEPT标记位的作用是让ViewGroup不再拦截事件,当然前提是ViewGroup不拦截ACTION_DOWN事件,ViewGroup若要拦截,则标记位设置了也无效

总结

我们最后完整的理一遍整个流程:一个点击事件首先由Activity接收,Activity调用dispatchTouchEvent进行分发,优先传递给Window进行处理,如果Window不消耗该事件则再由Activity的onTouchEvent来处理;Window处理过程则直接委托给DecorView进行事件分发,这个DecorView是android.R.id.content的父View,而android.R.id.content的子View就是我们Activity的视图view。
接下来就进入了View的事件分发过程
如果我们的Activity视图中的顶级ViewGroup拦截事件,即onInterceptTouchEvent返回true,则事件由该层ViewGroup处理,如果设置了onTouchListener,则onTouch被调用,否则onTouchEvent会被调用,如果还有onClickListener,则onClick最后被调用;若ViewGroup不拦截,则事件传递到点击事件链上的子View,子View的dispatchTouchEvent被调起,依上如此循环,完成事件分发。

注意点

上一篇下一篇

猜你喜欢

热点阅读