美文网首页Java&Android日更补完计划
View&ViewGroup的事件分发机制

View&ViewGroup的事件分发机制

作者: 埃赛尔 | 来源:发表于2017-11-27 18:22 被阅读5次

经过滴滴 尚德 秒拍 凡普金科 等这么多家公司的面试,这个问题每个公司都会被提问而且提问的方式也各有不同。但是百问不离其宗:你知道安卓的时间分发机制吗?一开始我对这种问题态度基本上都是不屑,因为最早面试的时候这种问题就是靠背面试题了解到的,到了后来写了这么多的自定义控件,自定义组合控件view 的事件机制具体是怎么一个逻辑也明白个七七八八,但是一直没有系统起来,反而在面试的时候说的模模糊糊,面试官的脸色瞬间就变了,蜜汁尴尬。

这是一个我自定义的ViewGroup,只是复写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent 在调用super方法前打印出方法名:

I/System.out: Activity:dispatchTouchEvent
I/System.out: dispatchTouchEvent
I/System.out: onInterceptTouchEvent
I/System.out: onTouchEvent
I/System.out: Activity:dispatchTouchEvent

1 由此可知每个ViewGroup 都经历
dispatchTouchEvent >>> onInterceptTouchEvent >>> onTouchEvent
其中 除了onTouchEvent是View的 其余dispatchTouchEvent,onInterceptTouchEvent都属于ViewGroup的方法
2 最先接收到事件的地方竟然是Activity的 dispatchTouchEvent
问题:那activity接触到事件是怎么分发下去的呢?

 /**
     * 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();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

从这段注释看来 这个方法是最早处理屏幕触摸事件。
问题:MotionEvent从何而来?
这个方法其实并不是activity本身的方法,该方法来自于Window.Callback 这个回调,并在activity的attach这个方法中mWindow.setCallback(this);

分析Activity的dispatchTouchEvent方法真正内容在这里:getWindow().superDispatchTouchEvent(ev) 我们都知道Activity的window的实现是PhoneWindow:

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

  @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

mDecor最顶层的view 进入源码查看:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks 

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

看来事件分发机制本质上就是从ViewGroup的dispatchTouchEvent开始的,

dispatchTouchEvent

 final boolean intercepted;
            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;
            }

onInterceptTouchEvent

intercepted = onInterceptTouchEvent(ev);

当ViewGroup不拦截事件的时候事件是如何向下分发

                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    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;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }

这段代码很长,但是大概逻辑还是能看明白的,就是遍历ViewGroup的所有子View,然后判断子view是不是能顺利接收到事件(有没有焦点,是否显示,能否接收到事件)主要还是要有两点来衡量:子元素是否在播放动画和事件的坐标是否在子元素内:

  if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

就是这两个方法。如果满足就会调用:

dispatchTransformedTouchEvent

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

看到源码就知道 这里实际上是调用子元素的dispatchTouchEvent,如果没有子元素 就直接调用super的dispatchTouchEvent方法。

 newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;

这里完成了mFirstTouchTarget的赋值,如果子元素的dispatchTouchEvent返回为false,ViewGroup就会把事件分发给下一个子元素(如果还有子元素的话),如果没有给mFirstTouchTarget赋值(mFirstTouchTarget=null)viewgroup将会拦截同一序列中的所有事件

我们都知道ViewGroup继承自View,当group没有自view的时候这些事件就交由View来处理了:

View的dispatchTouchEvent

   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)) {
                result = true;
            }

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

重点在这里:首先判断有没有设置touchListener之类的事件将事件传给监听,如果没有设置监听,view自己的onTouchEvent会响应

如果设置touchListener且返回true,其余三个条件可忽略,只会响应onTouch方法不会走view自己的onTouchEvent方法了

相关文章

网友评论

    本文标题:View&ViewGroup的事件分发机制

    本文链接:https://www.haomeiwen.com/subject/cwuxbxtx.html