前言

Android事件管理机制是一名专业Android研发工程师必须要了解的核心知识之一,深入了解一下该机制无论对我们日常开发还是找工作,乃至于对我们的架构思想都有很大的帮助。Android中我们用到的最多的事件是InputEvent,主要是两类:MotionEvent和KeyEvent。由于事件传递模型基本一致,我将以更常用到的MotionEvent为目标来讲解。

根据实际开发的需要,本文仅讲解touch事件在viewTree中的传递机制,为广大开发者搞清原理、扫清障碍。文章参考了Kelin童鞋的图解Android事件传递机制,写的很好大家也可以去看看。

本篇是图解Android系列第一篇,更多文章敬请关注后续文章。如果这篇文章对大家学习Android有帮助,还望大家多多转载。学习小组QQ群: 193765960。

版权归作者所有,如有转发,请注明文章出处:https://xiaodanchen.github.io/archives/


Touch事件的分发、传递、消费所涉及的类与函数

老规矩,让我们先来看一张类图:


Touch事件相关回调接口图谱
  • View类定义了两个Touch事件传递分发的函数:
    • public boolean dispatchTouchEvent(MotionEvent event)
    • public boolean onTouchEvent(MotionEvent event)
  • ViewGroup继承自View类,其重写了定义了两个 Touch事件传递分发的函数
    • @Override public boolean dispatchTouchEvent(MotionEvent ev)
    • public boolean onInterceptTouchEvent(MotionEvent ev)
  • Activity类定义了两个Touch事件传递分发的函数:
    • public boolean dispatchTouchEvent(MotionEvent event)
    • public boolean onTouchEvent(MotionEvent event)
  • Activity类的viewRoot实际上是PhoneWindow的DecorView(ViewGroup)
    • DecorView维护了一个LinearLayout对象,这个对象包括两部分:TitleView和ContentViews(FrameLayout)
    • 我们定义的layout.xml其实是被加载到TitleView和ContentViews中的

Touch事件在Activity的viewTree中的传递分发,如图

Touch事件在Activity的viewTree中的传递分发
  • 箭头的上面字代表方法返回值:return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  • dispatchTouchEvent和 onTouchEvent的框里有个[true---->消费]的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
  • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。

事件分发入口:Activity类的dispatchTouchEvent()方法

    /**     * 处理屏幕触摸事件。该方法是activity事件传递的入口。     * 你可以通过override这个方法来拦截所有touch事件使不传递给viewTree。     *      * @return 如果事件最终被消费将会返回 true.     */    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        //将事件分发给viewTree:以DecorView为RootView的viewTree        //DecorView为ViewGroup        if (getWindow().superDispatchTouchEvent(ev)) {            //viewTree存在元素消费了该事件,返回true            return true;        }‘                //viewTree不存在元素消费了该事件,调用activity的onTouchEvent(ev)        return onTouchEvent(ev);    }          /**     * 当activity中没有任何view可以消费该事件时将会调用该方法.      * 该方法常用来处理发生在activity的window边界之外的触摸事件。     * (联想:Dialog类型的activity点击外侧弹框消失应该是这货起的作用吧?)     *      * @return 如果你需要消费touch事件,返回true,否则返回false     * 默认是返回false的     */    public boolean onTouchEvent(MotionEvent event) {        if (mWindow.shouldCloseOnTouch(this, event)) {            finish();            return true;        }         return false;    }

ViewGroup 中touch事件是如何传递的

ViewGroup的dispatchTouchEvent()方法

     /**     * ViewGroup.java     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {                ...//次要代码,在此省略         boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {                     ...//次要代码,在此省略                         //标志是否拦截事件            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                ...                if (!disallowIntercept) {                    //当前ViewGroup是否需要拦截该事件                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action);                 } else {                    intercepted = false;                }            } else {                intercepted = true;            }                        ...            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            //如果当前ViewGroup不拦截该事件,则继续向叶子节点分发该事件            if (!canceled && !intercepted) {                ...                if (actionMasked == MotionEvent.ACTION_DOWN) {                    ...                    //叶子节点的数量                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        ...                        //根据Z-order得到拍好序的children                        final ArrayList preorderedList = buildOrderedChildList();                        ...                        //找到一个能接受Event的子View,再对子View的View树进行遍历                        final View[] children = mChildren;                        //遍历叶子节点                        for (int i = childrenCount - 1; i >= 0; i--) {                            ...                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(childIndex);                            ...                            //当前叶子view不能接收该touch事件,或者该叶子view不在触摸事件的触摸区域内,则查看下一个叶子                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                //跳出本次循环,遍历下一个叶子                                continue;                            }                                                      //找到了可以接收该事件并处于触摸区域内的叶子view: child                                                        newTouchTarget = getTouchTarget(child);                            //child已经存在target链表中                            if (newTouchTarget != null) {                                // 如果touch事件之前已经被该child消费,则后续touch事件不会继续向下层viewtree分发                                break;                            }                            //将事件分发给该叶子的viewTree                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                //叶子dispatch最终返回true                                ...                                //将child加入到target链表中                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                //终止遍历                                break;                            }                            //设置标志:当前遍历没有找到可消费事件的view,事件可继续正常分发                            ev.setTargetAccessibilityFocus(false);                        }                    }                }            }            // 找不到可以消费该事件的view            if (mFirstTouchTarget == null) {                 //继续事件传递流程                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                //找到可以消费该事件的view                while (target != null) {                    final TouchTarget next = target.next;                    //之前已经分发同类事件给了view                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        //返回true                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                         //将事件分发给他                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                          //返回true                            handled = true;                        }                    }                }            }        }        //返回ViewTree的事件传递消费结果:true 事件被消费;false 事件没有被消费        return handled;    }

ViewGroup的dispatchTransformedTouchEvent()方法

    /**     * 将touch事件根据view的坐标系进行变换,     * 如果view==null,则将事件传递给viewgroup继续分发事件     */    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,            View child, int desiredPointerIdBits) {        final boolean handled;        //cacel事件        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);            //cacel事件不需要转换,处理后在此返回处理结果            return handled;        }        ...        final MotionEvent transformedEvent;        if (newPointerIdBits == oldPointerIdBits) {            //child不可以消费该事件,继续分发            if (child == null || child.hasIdentityMatrix()) {                if (child == null) {                    //不存在可以消费该事件的child,父节点继续事件分发                    handled = super.dispatchTouchEvent(event);                } else {                    final float offsetX = mScrollX - child.mLeft;                    final float offsetY = mScrollY - child.mTop;                    event.offsetLocation(offsetX, offsetY);                    handled = child.dispatchTouchEvent(event);                    event.offsetLocation(-offsetX, -offsetY);                }                //返回分发结果                return handled;            }            //child可以消费该事件            transformedEvent = MotionEvent.obtain(event);        } else {            //手指点数变化            transformedEvent = event.split(newPointerIdBits);        }        if (child == null) {            // 手指点数变化后,child无法对事件消费,则父节点继续分发            handled = super.dispatchTouchEvent(transformedEvent);        } else {            //child可以消费该事件,坐标系变换            final float offsetX = mScrollX - child.mLeft;            final float offsetY = mScrollY - child.mTop;            transformedEvent.offsetLocation(offsetX, offsetY);            if (! child.hasIdentityMatrix()) {                transformedEvent.transform(child.getInverseMatrix());            }            //child分发事件            handled = child.dispatchTouchEvent(transformedEvent);        }               transformedEvent.recycle();        //返回child的分发处理结果        return handled;    }

ViewGroup的onInterceptTouchEvent()方法

    /**     * @return  true: 拦截事件并交给ViewGroup的onTouchEvent()处理     *          false: ViewGroup不消费事件,继续向子view分发事件     */    public boolean onInterceptTouchEvent(MotionEvent ev) {        //如果子类不override该方法,则默认返回false        return false;    }

View 中touch事件是如何传递的

View的onInterceptTouchEvent()方法

    /**     * 将屏幕的touch事件传递给目标view或者view自己(view本身就是目标view)     *      * @return True if the event was handled by the view, false otherwise.     */    public boolean dispatchTouchEvent(MotionEvent event) {        ...        boolean result = false;         if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }         final int actionMasked = event.getActionMasked();        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Defensive cleanup for new gesture            stopNestedScroll();        }         if (onFilterTouchEventForSecurity(event)) {            ListenerInfo li = mListenerInfo;            //如果view设置了touchlistener并且view是enable状态,则在listener中处理事件            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            //lsitener未处理(包括未定义listener),则交给ontouchEvent()            if (!result && onTouchEvent(event)) {                result = true;            }        }         //如果该view未消费该事件,则标记event交由父节点处理        if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }                if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result;    }

View的onTouchEvent()方法

    /**     * Implement this method to handle touch screen motion events.     * @return True if the event was handled, false otherwise.     */    public boolean onTouchEvent(MotionEvent event) {        final float x = event.getX();        final float y = event.getY();        final int viewFlags = mViewFlags;        final int action = event.getAction();        if ((viewFlags & ENABLED_MASK) == DISABLED) {            //view如果是disabled状态,该view仍然消费该事件,但是不会做出UI的相应            return (((viewFlags & CLICKABLE) == CLICKABLE                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);        }        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {            switch (action) {                case MotionEvent.ACTION_UP:                    ...                    break;                case MotionEvent.ACTION_DOWN:                    ...                    }                    break;                case MotionEvent.ACTION_CANCEL:                    ...                    break;                case MotionEvent.ACTION_MOVE:                    ...                    break;            }            return true;        }        return false;    }

如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。

U型事件传递模型

注意:这张图只是一张事件的传递模型的示意图,事件传递的实际实现细节跟图中不太一致,但最终事件的传递方向是一致的。

通过代码我们注意到,每一层的dispatchtouchevent()都是该层的事件的入口,在每一层的逻辑都大致遵循如下调用规则:

  • dispatchtouchevent捕获到上层分发过来的事件
  • 调用oninterceptTouchevent用来逻辑判断该事件是否需要在本层处理
  • 如果oninterceptTouchevent 返回false,则调用下一层的viewtree的dispatchtouchevent(递归),子viewtree的返回结果会作为本层dispatchtouchevent的结果返回
  • 如果oninterceptTouchevent返回true,则调用本层的ontouchevent方法
  • ontouchevent的逻辑处理结果会返回给dispatchtouchevent座位结果返回
  • 最终本层的dispatchtouchevent的处理结果会返回给父view

整个viewtree其实都是在按照同样的逻辑进行着层层的递归。
希望读者能够好好的把握一下代码和递归逻辑,这样在我们的view中就可以根据实际需要灵活使用时间分发、拦截和处理的三种接口,灵活的控制事件的传递和消费。

ACTION_MOVE和ACTION_UP事件

我们上文中讲到的事件传递流程是ACTION_DOWN的处理流程。
由于ACTION_DOWN事件是touch事件的第一个事件,所以其处理流程会相对复杂。而后续的一系列其他事件,其处理逻辑收到ACTION_DOWN事件的处理结果的影响而更加的智能。系统不会傻傻的把前人走过的死路让后人再走一遍。换句话说,一旦ACTION_DOWN事件找到了target,后续的一些列事件就会直达target,而不会再分发往更底层进行逻辑迭代。
还是那句话,看图(再次感谢kelin童鞋做出了这么优雅的图片):


ACTION_MOVE和ACTION_UP事件
  • 红色的箭头代表ACTION_DOWN 事件的流向
  • 蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

本篇是Glide框架及源码解析的第一篇,更多文章敬请关注后续文章。版权归作者所有,如有转发,请注明文章出处:原文链接)

更多相关文章

  1. Android(安卓)gallery 3D效果
  2. Android官方数据绑定框架DataBinding
  3. Android事件分发机制及源码分析
  4. Android的五大基本组件
  5. Android中使用广播机制退出多个Activity
  6. android解析XML文件的三方法之Pull(读写)
  7. Android中的事件传递机制
  8. android 的Java代码中的布局相关方法LayoutParams
  9. Android添加用户组及自定义App权限的方法

随机推荐

  1. android ksoap用法
  2. Android(安卓)java.lang.NoSuchMethodErr
  3. Android调用百度地图API实现――实时定位
  4. android 群发短信
  5. Unable to start activity ComponentInfo
  6. android,总结,判断应用程序是否切换到后
  7. Monkey测试结果解析(二)
  8. How to set up a link betwteen a real A
  9. Android(安卓)之 setContentView 源码阅
  10. Android(安卓)电话拨号