Android View事件分发没那么复杂--极简化流程(核心源码)

从ViewRootImpl到Decorview, 从DecorView交给Activity, Activity通过windowphone从DecorView开始分发.

自上而下思维,本文梳理枝干,不关注太细节的东西。

java 复制代码
#DecorView.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
//mWindow.getCallback()获取的就是Activity本身.
scss 复制代码
#Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
csharp 复制代码
#PhoneWindow.java

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
csharp 复制代码
#DecorView.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    //调用父类ViewGroup的分发方法开始真正View体系中的事件分发
    return super.dispatchTouchEvent(event);
}

后面就是ViewGroup的具体分发流程.

VG重写了继承的父类View的dispatchTouchEvent()

return true则代表消费时间,停止继续分发。反之则反之。

关键核心代码。

java 复制代码
#ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    if (!canceled && !intercepted) {
        //如果不拦截,会遍历子View进行分发事件
        for (int i = childrenCount - 1; i >= 0; i--) {
            
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            //找到了需要消费的子View--child,下面代码会将mFirstTouchTarget赋值为该child
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            
        }
    }
    ...
    //如果拦截或者没有子View消费事件,此处mFirstTouchTarget会为空,
    //会将child参数设为null。其实就是看vg自己是否要消费事件
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    }
    //如果mFirstTouchTarget!=null,说明没有拦截并且子View消费了事件,
    //那么该vg的事件分发其实已经在上面整个流程的代码中走完,下面是一些杂项处理。
    ...
            
    return handled;
}


private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
        ...
        
        if (child == null) {
            //child为空,说明VG拦截了事件或者没有找到可以消费事件的子View,
            //那么就调用父类View的dispatchTouchEvent()来自己处理
            handled = super.dispatchTouchEvent(event);
        } else { 
            //此处将事件分发给子View,如果所有子View都不处理那么其实又会
            //调用上面child == null的代理,结合上面dispatchTouchEvent中流程去理解。
            //如果子View也是个vg,那么又会调用该vg的dispatchTouchEvent()。嵌套调用
            handled = child.dispatchTouchEvent(event);
        }
        ...
        
        return handled;
}

VG先判断是否拦截,如果拦截

则交个自己重写的的dispatchtouchevent()处理,依次调用onTouchListener.onTouch()和onTouchEvent(),onTouchEvent()内部又会调用onClickListerner.click()的回调如果前面的调用消费了事件则不会走到后面。 如果本身并没有消费事件,那么返回false

向上传递.

ini 复制代码
#View.java

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    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;
    }
    ...
}

如果某个view消费了事件,那么vg会记录该view,后面的事件都会交给其处理.

VG如果不拦截事件

那么VG就会遍历所有子View,交给子View的dispatchtouchevent()处理。内部流程和上面VG调用父类View的dispatchTouchEvent()是一样的。 依次调用onTouchListener.onTouch()和onTouchEvent(),onTouchEvent()内部又会调用onClickListerner.click()的回调如果前面的消费则不会走到后面。

VG判断所有子view都未消费事件,则会判断自身是否需要消费,交给自己继承的父类View的dispatchtouchevent(),也就是调用super.dispatchtouchevent()方法,这和上面拦截时流程又一致了。如果VG本身也都没有消费,那么返回false。

继续向上传递. 重复此过程


如果都未消费,最后到达Activity,如果也未消费,直到又回到DecorView.最终到达ViewRootImpl的责任链的下一个节点继续处理。


如有出入的部分,还请指教

相关推荐
幻雨様2 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端3 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.4 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton5 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw9 小时前
安卓图片性能优化技巧
android
风往哪边走9 小时前
自定义底部筛选弹框
android
Yyyy48210 小时前
MyCAT基础概念
android
Android轮子哥10 小时前
尝试解决 Android 适配最后一公里
android
雨白11 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走12 小时前
自定义仿日历组件弹框
android