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

事件传递方向

activity -> viewGroup -> view

事件的传递入口

事件触发 -> 硬件 -> 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。

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/接收到,一路继续向上传递,最终完成了事件路径的记录。

事件传递的完整路径

总结

事件的传递回顾

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

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

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。喜欢的小伙伴可以关注一下哦。谢谢!

发布了289 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45365889/article/details/102590266