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的责任链的下一个节点继续处理。


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

相关推荐
闲暇部落1 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX3 小时前
Android 分区相关介绍
android
大白要努力!4 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee4 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen9 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年17 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿19 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神20 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri