美文网首页Android进阶之路Android自定义View事件分发
事件分发:事件的传递和传递路径

事件分发:事件的传递和传递路径

作者: space0o0 | 来源:发表于2019-10-16 16:47 被阅读0次

    事件传递方向
    activity -> viewGroup -> view


    事件序列.png

    事件的传递入口

    事件触发 -> 硬件 -> Native -> 通过JNI ->
    Activity.dispatchTouchEvent() ->
    PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() ->
    ViewGroup.dispatchTouchEvent()

    事件的开始

    事件的触发从手指触摸屏幕开始,屏幕硬件接受到触摸事件后,交由底层系统处理(native层)。native层再通过JNI通知Activity,触发dispatchTouchEvent方法,开始事件在view中的一系列传递。

    事件的传递

    Activity对事件的传递最终是交给DecorView(顶层容器)处理,DecorView继承自FrameLayout,也是一个ViewGroup。

    事件的传递始终是在ViewGroup 和 View中逐层传递。

    ViewGroup控制事件传递的3个方法

    * dispatchTouchEvent;事件分发,向下传递事件
    * onInterceptTouchEvent;事件拦截
    * onTouchEvent;事件消费/处理
    

    View控制事件传递的2个方法

    * dispatchTouchEvent;事件分发,最终调用事件处理
    * onTouchEvent;事件消费/处理
    

    需要注意两个dispatchTouchEvent方法:ViewGroup继承自View,重写了View的dispatchTouchEvent方法,主要功能是对 子View 或 子ViewGroup进行事件的传递;而View的dispatchTouchEvent方法,主要功能是调用事件处理的前期工作。

    本文源码版本,api:28

    Activity中的传递路径

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//空方法,重写用于事件触发时被调用
        }
        //最终调用DecorView的dispatchTouchEvent()
          //即ViewGroup的dispatchTouchEvent()
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

    Activity的dispatchTouchEvent()分两种情况:
    事件被消费,return true
    没有被消费,调用activity的onTouchEvent方法

    ViewGroup中的传递路径

    //ViewGroup的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()。

    拓展点
    disallowIntercept :不允许拦截的标记。
    子view可以调用父容器的/requestDisallowInterceptTouchEvent/方法对其进行修改。

    //ViewGroup的dispatchTouchEvent()中 
    for (int i = childrenCount - 1; i >= 0; i—) {
        final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(
                preorderedList, children, childIndex);
    。。。
    //寻找可传递的子view代码
    。。。
          //重点1
        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();
              //重点2
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }
    。。。
    }
    
    //——————————dispatchTransformedTouchEvent()——————————
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
    
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    //——————————dispatchTransformedTouchEvent()——————————
    

    遍历查找可传递事件的子View。

    重点1
    在寻找到子View后,调用dispatchTransformedTouchEvent()。
    子View如果为空,就调用自己的父类方法
    子View不为空,就调用子View的事件分发,向下一层View传递事件。

    子View继续重复dispatchTouchEvent()方法,类似于递归方法的调用。
    等待返回handled值。

    举个例子:当前有ViewGroupA,ViewGroupB,ViewA。ViewGroup包裹ViewGroupB,ViewGroupB包裹ViewA。

    2.png

    重点2
    addTouchTarget方法

    主要功能:记录被消费的事件传递过程中每个view,形成完整的事件传递路径。

    下次的MOVE,UP事件等都只需要查找传递路径即可分发事件,不必再重复查找View。

    View的事件处理

    //View的dispatchTouchEvent()
    。。。
    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;
    }
    。。。
    

    首先判断ListenerInfo是否为空,ListenerInfo中包含各种点击,双击,长按等监听。所以这类监听的优先级是高于onTouchEvent。

    在没有设置监听的情况下,调用onTouchEvent方法,如果onTouchEvent方法进行了消费,返回true。

    View的/dispatchTouchEvent/返回的true,被父容器ViewGroup的/dispatchTransformedTouchEvent/接收到,一路继续向上传递,最终完成了事件路径的记录。

    事件传递的完整路径

    ToucheEvent.png

    总结

    事件的传递回顾

    • Activity接受事件,交给ViewGroup处理。
    • ViewGroup遍历子View,递归查找被消费的子View。
    • 记录递归handled=true的View,形成完整的事件传递链。
    • 其余一系列事件直接经过 传递链 传递 事件。

    ViewGroup的3个方法和View的2个方法之间的传递过程比较简单。
    关键点是ViewGroup的向下传递,递归查找消费子View。

    相关文章

      网友评论

        本文标题:事件分发:事件的传递和传递路径

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