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

相关文章

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

浅析CSS 中z - index属性的作用及在什么情况下会失效

《浅析CSS中z-index属性的作用及在什么情况下会失效》z-index属性用于控制元素的堆叠顺序,值越大,元素越显示在上层,它需要元素具有定位属性(如relative、absolute、fi... 目录1. z-index 属性的作用2. z-index 失效的情况2.1 元素没有定位属性2.2 元素处

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D

Android如何获取当前CPU频率和占用率

《Android如何获取当前CPU频率和占用率》最近在优化App的性能,需要获取当前CPU视频频率和占用率,所以本文小编就来和大家总结一下如何在Android中获取当前CPU频率和占用率吧... 最近在优化 App 的性能,需要获取当前 CPU视频频率和占用率,通过查询资料,大致思路如下:目前没有标准的

spring @EventListener 事件与监听的示例详解

《spring@EventListener事件与监听的示例详解》本文介绍了自定义Spring事件和监听器的方法,包括如何发布事件、监听事件以及如何处理异步事件,通过示例代码和日志,展示了事件的顺序... 目录1、自定义Application Event2、自定义监听3、测试4、源代码5、其他5.1 顺序执行

浅析Python中的绝对导入与相对导入

《浅析Python中的绝对导入与相对导入》这篇文章主要为大家详细介绍了Python中的绝对导入与相对导入的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1 Imports快速介绍2 import语句的语法2.1 基本使用2.2 导入声明的样式3 绝对import和相对i

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问