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学习总结之Java和kotlin区别超详细分析

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

浅析Java如何保护敏感数据

《浅析Java如何保护敏感数据》在当今数字化时代,数据安全成为了软件开发中至关重要的课题,本文将深入探讨Java安全领域,聚焦于敏感数据保护的策略与实践,感兴趣的小伙伴可以了解下... 目录一、Java 安全的重要性二、敏感数据加密技术(一)对称加密(二)非对称加密三、敏感数据的访问控制(一)基于角色的访问

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

浅析如何使用xstream实现javaBean与xml互转

《浅析如何使用xstream实现javaBean与xml互转》XStream是一个用于将Java对象与XML之间进行转换的库,它非常简单易用,下面将详细介绍如何使用XStream实现JavaBean与... 目录1. 引入依赖2. 定义 JavaBean3. JavaBean 转 XML4. XML 转 J

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel