Android中的事件分发浅析

2024-09-03 04:08
文章标签 android 事件 浅析 分发

本文主要是介绍Android中的事件分发浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说在前面

Android在启动一个activity时,会实例化一个PhoneWindow,而PhoneWindow对象持有一个Decorview的引用,DecorView中有个id为content的ViewGrou,而我们平常在xml写的布局,就是加载在这个ViewGroup中的,如下图所示(图片引用自网络,侵删)。

 事件分发

一般开发过程中,我们需要处理的事件,也就是我们的手指在ContentView上的触摸事件,在我们眼中,我们用手指触摸、点击一个个按钮、图片等,而无论是按钮、图片 等等,都无非是以下两种的分支:

1、View

拥有dispatchTouchEvent、onTouchEvent两个方法,分管事件分发、事件消费

2、ViewGroup

拥有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法,分管事件分发、事件拦截、事件消费

预先准备

我们先准备一个View以及两个ViewGroup,他们分别是ChildView(黑色)、MotherLayout(红色)、FatherLayout(蓝色),Child盖在Mother上,Mother盖在Father上,所以Father是三者的最高层级如下图所示: 

 我们分别重写三个类的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法并打上log,具体源码为:

FatherLayout.kt

class FatherLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs){private val TAG="FatherLayout"companion object{fun getActionName(action:Int?):String{return when(action){0->{"按下"}1->{"抬起"}2->{"滑动"}else->{"其他"}}}}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的onIntercept方法,事件为:${getActionName(ev?.action)}")return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的onTouchEvent方法,事件为:${getActionName(ev?.action)}")return super.onTouchEvent(ev)}
}

MotherLayout.kt 

class MotherLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs){private val TAG="MotherLayout"override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的onIntercept方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onTouchEvent(ev)}}

ChildView.kt

class ChildView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {private val TAG = "ChildView"override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了孩子的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了孩子的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onTouchEvent(ev)}
}

xml布局

<?xml version="1.0" encoding="utf-8"?>
<com.wp.facetest.eventHandle.FatherLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="450dp"android:layout_gravity="center"android:layout_height="450dp"android:background="#3a7dff"><com.wp.facetest.eventHandle.MotherLayoutandroid:layout_width="300dp"android:layout_gravity="center"android:layout_height="300dp"android:background="#ff3a7d"><com.wp.facetest.eventHandle.ChildViewandroid:layout_width="150dp"android:layout_gravity="center"android:layout_height="150dp"android:background="#333" /></com.wp.facetest.eventHandle.MotherLayout>
</com.wp.facetest.eventHandle.FatherLayout>

开始测试

我们先后单击Father、Mother、Child,得出如下日志及流程图:

PS:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法默认返回false

单击Father

单击Mother

单击Child

 以上可以看出,默认情况下,点击一个布局后,onDown(按下)事件首先会传导到最上层的父布局(FatherLayout)的dispatchTouchEvent方法,紧接着onInterceptTouchEvent、onTouchEvent,然后传到次一层的ViewGroup或View(MotherLayout或ChildView),重复以上传导,如果当前布局无子布局,则会在当前布局的onTouchEvent调用后,依次向上调用对应层次父布局的onTouchEvent,然后本次点击事件结束。

正常来说,一次单击动作,是包含Down和Up两个事件的,那么为什么以上只发现了Down事件呢?

原因是从父到子,无一个敢于站出来承担这次的Down事件,那么上级领导自然没必要再将Up事件下放下来,只能自己默默把Down和Up事件一起消化掉~

那么,我们让父、母、子勇敢一点,会发生什么呢?

针对dispatchTouchEvent

1、将FatherLayout的dispatchTouchEvent删除super.dispatchTouchEvent(ev),并且分别返回true和false,随后点击FatherLayout、MotherLayout、ChildView任何一个,代码、日志分别如下:

返回true

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return true}

返回false

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return false}

当dispatchTouchEvent删除super.dispatchTouchEvent(ev)时,无论返回值为true还是false,事件都不会往下传递,也不会传递到本身的onInterceptTouchEvent和onTouchEvent,只不过,如果返回值为true,则可以接收到所有的事件,如果返回false,只能收到第一个事件。

2、将FatherLayout的dispatchTouchEvent保留super.dispatchTouchEvent(ev),并且返回true(无需试验false,前面默认的就是false),随后点击ChildView,代码、日志分别如下:

 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")super.dispatchTouchEvent(ev)return true}

可以看出,第一次的Down事件,在传递到MotherLayout、ChildView后,发现没有人处理,所以和我们上面发的流程图一样,通过onTouchEvent自下而上传回,而随后的Up事件也不会继续往下传递,只到FatherLayout这里截止。

接下来,我们在MotherLayout中也针对dispatchTouchEvent做类似处理:


3、将FatherLayout和MotherLayout的dispatchTouchEvent都保留super.dispatchTouchEvent(ev),并且都返回true,随后点击ChildView,日志如下:

我们发现Down事件从ChildView往上传递,到了MotherLayout这里截止了,而Up事件也只传到了MotherLayout这里,没有继续往下传递

4、将FatherLayout和MotherLayout的dispatchTouchEvent都保留super.dispatchTouchEvent(ev),并且FatherLayout返回true、MotherLayout返回false,随后点击ChildView,日志如下:

我们发现Down事件从ChildView往上传递,还是到了MotherLayout这里截止了,但是后续的Up事件却不见了,原因是FatherLayout的dispatchTouchEvent返回了false,FatherLayout的上级领导看FatherLayout不作为,肯定不会再下放Up事件了!

所以,我们对dispatchTouchEvent得出结论:

1、如果删除super.dispatchTouchEvent(ev),则无论返回true还是false都无意义,因为这个时候只有dispatchTouchEvent可以收到事件,而onInterceptTouchEvent和onTouchEvent都收不到事件,所以我们平时处理事件分发不建议这样写。

2、保留super.dispatchTouchEvent(ev),返回 true,如果下级布局的dispatchTouchEvent都没有返回true,则此布局为第一责任人,下层的Down事件传到此后不会再往上传递,而后续的Up事件也只会从上传到此,不会往下传递。

3、保留super.dispatchTouchEvent(ev),返回 false,那么这就又回到无人敢承担责任这一说了,第一次的Down事件会传递到最下层(ChildView),随后再层层往上传递,然后第二次的Up事件则不会再传下来。

4、如果一个布局想要处理事件,首先自己的dispatchTouchEvent要返回true,其次他的所有父布局的dispatchTouchEvent也要返回true,正所谓,你想干成事,首先要争取到领导的支持,你的上级领导是个不作为的人,你做起事来,肯定是难度很大的。

针对onInterceptTouchEvent

咱们先看一下ViewGroup里onInterceptTouchEvent的源码:

 public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;}

根据一些逻辑,判断该返回true还是false,那这一次我们不管super.onInterceptTouchEvent,自己定规则!考虑到

1、将FatherLayout的onInterceptTouchEvent返回true,随后点击ChildView,日志如下:

我们发现事件被FatherLayout拦截了,没有往下传递。

2、将FatherLayout的onInterceptTouchEvent返回false、MotherLayout的onInterceptTouchEvent返回true,随后点击ChildView,日志如下

 我们发现事件被MotherLayout拦截了,但是我们发现,Down事件还是会往上传递!

所以,我们对onInterceptTouchEvent得出结论:

1、onInterceptTouchEvent返回true,事件被拦截,不会往下传递,但是还是会往上传递,且后续的Up事件不会传递下来,简单点说:接了活,但是没有能力干,也不分给下属,最后任务被上级领导收回了。

针对onTouchEvent

观察View的onTouchEvent源码得知,此方法处理一些点击事件等,所以我们不调用super.onTouchEvent,简单粗暴的返回true和false。

1、将FatherLayout的onTouchEvent返回true,随后点击ChildView,代码、日志如下:

override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了父亲的onTouchEvent方法,事件为:${getActionName(ev?.action)}")return true}

我们看出,Down事件看不出什么,但是Up事件到FatherView的onTouchEvent后,没有继续传递

2、将FatherLayout的onTouchEvent返回false,将MotherLayout的onTouchEvent返回false,随后点击ChildView,代码、日志如下:

  override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return true}

 我们发现,由于ChildView没有处理Down事件,然后传给了MotherLayout,但是母亲处理了Down,所以没有继续向上传递,而Up事件,也是在母亲的onTouchEvent这里截止了。

感觉onTouchEvent返回true和dispatchTouchEvent返回true的效果很类似啊,那是因为在View的dispatchTouchEvent中,有一行代码如下:

if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo 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;}}

result在最后会根据onTouchEvent的返回值来定,所以,如果dispatchTouchEvent 返回super.dispatchTouchEvent,且onTouchEvent返回true,那么dispatchTouchEvent像相当于返回了true,所以现象会和上面一致。

那么,我们将dispatchTouchEvent返回false,且onTouchEvent返回true,看一下效果:

3、将MotherLayout的dispatchTouchEvent返回false,onTouchEvent返回true,然后点击ChildView,代码和日志如下:

 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")super.dispatchTouchEvent(ev)return false}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return true}

可以得出:只要dispatchTouchEvent返回false,onTouchEvent返回true也是没有用的。你没有接这个任务,但是背地里偷偷去处理这个任务,最终,上级领导也是不买账的,因为你不是责任人!

接下来,整理最终结论:

1、dispatchTouchEvent管调度,这个不返回true,其他都免谈。

2、onInterceptTouchEvent管拦截,返回true就代表下层布局没资格处理任何事件。

3、onTouchEvent管处理,真正处理事件的地方。

本博讲述的,也只是三个方法比较浅显的理解,真正深入的理解,还是要进入系统类的源码中寻找~

这篇关于Android中的事件分发浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131981

相关文章

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio