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


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

相关推荐
晨曦_子画9 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
孤客网络科技工作室31 分钟前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
Mr Lee_2 小时前
android 配置鼠标右键快捷对apk进行反编译
android
顾北川_野2 小时前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
&岁月不待人&2 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
Winston Wood4 小时前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽4 小时前
Android 项目模型配置管理
android
帅得不敢出门5 小时前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
problc5 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
帅得不敢出门16 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡