Android 事件分发:为什么有时候会出现事件冲突?事件的顺序是如何的?出现事件冲突如何解决呢?比如为什么左右可以滑动,而上下却不行?

本文主要是介绍Android 事件分发:为什么有时候会出现事件冲突?事件的顺序是如何的?出现事件冲突如何解决呢?比如为什么左右可以滑动,而上下却不行?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录:

在这里插入图片描述


一、为什么要学习事件呢?

在这里插入图片描述

1.在开发复杂的应用时,经常需要处理复杂的用户交互逻辑。学习事件分发机制可以帮助你更好地控制事件的传递和处理流程,从而解决一些复杂的交互问题,如滑动冲突、点击穿透等。

2.面试需要:事件冲突的原因是什么?在开发过程中,可能会遇到一些与事件处理相关的问题,如事件没有被正确传递、事件被错误地拦截等。了解事件分发机制可以帮助你更快地定位问题所在,并找到解决方案。



二、事件是什么?事件是如何触发的?

事件(Event)是用户与应用程序界面(UI)交互时发生的动作或情况的抽象表示。这些事件可以是由用户通过触摸屏幕、按键、旋转设备等方式触发的,也可以是由系统产生的,比如系统状态变化(如屏幕旋转、电量变化)等。

当事件发生时,Android系统会将该事件发送给注册了该事件监听器的组件。然后,事件监听器中的相应方法会被调用,执行开发者定义的事件处理逻辑。



三、事件冲突是什么:举例说明,比如ViewPage和Recyclerview一起使用,会出现左右不能滑动或上下不能滑动的情况

遇到的问题

2.1 ViewPager的代码

class MyViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) {override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {return false}
}

2.2 RecyclerView的代码

class MyRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView(context, attrs) {override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {return super.dispatchTouchEvent(ev)}
}

出现了上下可以滑动,但是左右不可以。到底是什么原因导致的呢?

首先我们先了解一下事件的流程是怎么样的。



四、事件的大致流程

事件产生的原因,如下图:1个事件的发生,通过触摸手机屏幕——>导电——>传感器——>Linux——>Activity里面,再传到ViewGroup,最终再传到 View。

那么Activity里面,我们需要关注dispaatchToucheEvent方法。这里需要注意,ViewGrouper的dispaatchToucheEvent和View的dispaatchToucheEvent是做着不同的事情。后面我们会重点讲解

Android事件的处理流程是一个从硬件到软件、从底层到高层的复杂过程,涉及到底层硬件、Linux内核、Android框架以及最终的应用层。

这里我们只需要关注ViewGroup的dispaatchToucheEvent,以及View的dispaatchToucheEvent。也是下面我们需要重点讲解的



五、事件分发和事件处理是什么意思?

5.1 事件分发

事件分发,指在安卓系统中,如何处理和传递用户交互事件的一套机制,当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。这也是事件分发存在的目的,控件太多,他不知道具体是哪一个,所以他需要通过分发的方式,递归去找到这个事件的处理者

5.2 事件处理

事件处理是指当事件被传递到相应的组件后,该组件执行具体的操作来响应事件的过程。如处理触摸事件、点击事件等。

下面我们一起看看源码,了解一下他是如何进行事件分发的,如何事件处理的,从中找出如何解决事件冲突的方案。


六、事件处理源码分析

我们先看看事件处理的源码,从比较简单的先开始。事件分发留到后面。

比如我们现在给一个控件设置了点击事件和触摸事件。如下:

  btnEvent.setOnClickListener {Log.d(TAG, "onClick: ")}btnEvent.setOnTouchListener { view, event ->Log.d(TAG, "OnTouch: "+event.action)false}
  1. 那么你觉得哪个会先执行,事件的优先级是如何的,以及你可以如何去拦截事件的处理?日志如下:

在这里插入图片描述
可以看到onTouch方法的优先度会高,为什么呢?我们看看源码:

在这里插入图片描述
在dispatchTouchEvent方法中,如果控件注册了onTouchListener,则会先执行onTouch回调方法。

btnEvent.setOnClickListener {Log.d(TAG, "onClick: ")}btnEvent.setOnTouchListener { view, event ->Log.d(TAG, "OnTouch: "+event.action)true}

在这里插入图片描述

如果onTouch方法返回true,表示该事件已被消费,不会继续传递;如果返回false,则事件会继续传递给onTouchEvent方法。
onTouchEvent方法会处理包括ACTION_DOWN、ACTION_MOVE、ACTION_UP等在内的事件。当事件为ACTION_UP时,如果控件是可点击的(如按钮),则会调用performClick方法,进而触发onClick方法。

2.该事件已被消费,不会继续传递

在这里插入图片描述

  1. 我们看看继续传递的情况,看看onClick在哪里

在这里插入图片描述
因为点击事件是松开后触发的,所以我们要找到松开的地方。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

好的,事件处理的源码我们就看到这里,我们看看事件分发。


七、事件分发源码分析:我们会将源码划分为三块来讲

我们上面的例子,ViewPager+Recyclerview,Viewpager就是容器,而Recyclerview就是子view,下面我们就代入进去分析代码。

7.1 第一块源码:是否拦截子view,也就是,是否将事件分发给子view来出来,还是说自己处理


举例:如果我们的ViewPager的onInterceptTouchEvent方法返回了true,那么就拦截了子view,返回false,就不拦截子view,让他分发给子view处理事件。

在这里插入图片描述


7.2 第二块源码:不拦截子view,那么就开始遍历子view,看谁是否需要进行处理事件

如下是第二块代码,主要进行子view的事件分发,如果intercepted为false,那么就进来了。如果为true,那么第二块代码相当于什么都不执行,直接到第三块代码。

在这里插入图片描述


7.3 第三块源码:如果拦截了子view,那么就循环自己(ViewPager)是否需要处理事件,如果没有拦截,那么这里会看哪个子view需要进行事件的处理

在这里插入图片描述

7.4 具体案例

案例一:假如ViewPager里面直接返回true:RecyclerView的上下滑动事件,就不会得到执行。这是为什么?

class MyViewPager(context: Context) : ViewPager(context) {override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {return true}
}

其实我们看第一块源码我们就已经知道了答案,如果onInterceptTouchEvent返回true,就会进行拦截,那么就不会进行子View的事件分发。[效果如下gif]

在这里插入图片描述

案例二:为什么Viewpager返回false,RecyclerView不用设置,左右不能滑动了,但是上下能滑动,为什么呢?[效果如下gif]

在这里插入图片描述首先,我们看看源码,第一块肯定是通过了,我们看第二块。首先要拿到所有的子view,我们这里可能只有一个,但也有可能写多个,所以我们要拿到所有的子view,通过buildTouchDispatchChildList方法。
在这里插入图片描述拿到所有的子view以后,我们要进行倒序处理

在这里插入图片描述
为什么呢?我们知道,我们写三个控件,他们是层层叠加的,比如Viewpager+RecyclerView,那么Viewpager是在最底部,RecyclerView则是在上面,Viewpager就是0,RecyclerView就是1,所以我们要倒序过来,处理最上层view,看他是否需要处理事件,如果不处理,才到Viewpager。

如下代码,是判断是否点到了view的位置。不满足,肯定是不需要执行事件咯。所以我们设置了点击事件,但是没点击到,肯定就不会执行。
在这里插入图片描述如果ViewPager和RecyclerView都返回false,为什么viewpager的可以得到事件处理呢?因为他最终都会进行处理。一旦子view处理了事件,就不会在给父容器处理事件了。因为事件,始终只有一个人处理。

所以,如果ViewPager返回了false,而RecyclerView返回true,那么RecyclerView就能上下滑动,而ViewPager就不行了。

7.5 Down事件

Down事件其实就是我们上面说的这些代码。前面我们已经知道事件冲突的原因,因为事件,始终只有一个人处理。那么我们如何解决他呢,就需要我们了解move事件分发流程。

7.6 Move事件

我们先看看结论,看看是如何解决的。

class MyViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) {override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {if (ev?.action == MotionEvent.ACTION_DOWN) {super.onInterceptTouchEvent(ev)return false}return true}
}
class MyRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView(context, attrs) {var mLastX:Float = 0fvar mLastY:Float = 0foverride fun dispatchTouchEvent(ev: MotionEvent?): Boolean {var x = ev?.getX()var y = ev?.getY()when (ev?.action) {MotionEvent.ACTION_DOWN -> {requestDisallowInterceptTouchEvent(true)}MotionEvent.ACTION_MOVE -> {val deltaX = (x!! - mLastX).toInt()val deltaY = (y!! - mLastY).toInt()if (Math.abs(deltaX)>Math.abs(deltaY)){requestDisallowInterceptTouchEvent(false)}}MotionEvent.ACTION_UP -> {}else -> {}}mLastX = x!!;mLastY = y!!;return super.dispatchTouchEvent(ev)}
}

首先,我们先了解一下,起始事件,是MotionEvent.ACTION_DOWN,也就是按下事件,大家都同意吧,那么这个时候,我们不要拦截子View,所以我们就返回了false.

if (ev?.action == MotionEvent.ACTION_DOWN) {super.onInterceptTouchEvent(ev)return false}

当滑动的事件的时候,那么可能是子View要处理,也可能是父View要处理,所以子View里面是这样写的

 when (ev?.action) {MotionEvent.ACTION_DOWN -> {requestDisallowInterceptTouchEvent(true)}MotionEvent.ACTION_MOVE -> {val deltaX = (x!! - mLastX).toInt()val deltaY = (y!! - mLastY).toInt()if (Math.abs(deltaX)>Math.abs(deltaY)){requestDisallowInterceptTouchEvent(false)}}MotionEvent.ACTION_UP -> {}else -> {}}

requestDisallowInterceptTouchEvent(true)究竟是什么?如果子视图调用了这个方法,并且返回了 true,那么父视图将不会调用其 onInterceptTouchEvent(MotionEvent ev) 方法来尝试拦截这个触摸事件,也就是说子类一定会得到处理。除非后续的事件(如 ACTION_MOVE 或 ACTION_UP)改变了这个决策。所以我们在子View里面进行了处理,如果是左右方向,那么就可以让父容器去拦截,如果是上下方向,就不要让父容器拦截,让子view去进行处理。

为什么可以这样呢,因为Move事件不是触发一次,而是多次。

我们进入requestDisallowInterceptTouchEvent方法里面去看看。
在这里插入图片描述
这里设置了条件以后,我们可以到第一块代码这看看,因为第一块代码是决定是否分发事件给子View。
在这里插入图片描述

强行使其给子View进行事件的分发。所以我们需要requestDisallowInterceptTouchEvent分发来进行处理。

好了,那么事件的介绍就分享到这里了~

八、学习总结

事件分发的源码第一次看比较复杂,需要反复观看,才能理解,并且结合控件案例去测试,效果会更好。

这篇关于Android 事件分发:为什么有时候会出现事件冲突?事件的顺序是如何的?出现事件冲突如何解决呢?比如为什么左右可以滑动,而上下却不行?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

Mysql DATETIME 毫秒坑的解决

《MysqlDATETIME毫秒坑的解决》本文主要介绍了MysqlDATETIME毫秒坑的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 今天写代码突发一个诡异的 bug,代码逻辑大概如下。1. 新增退款单记录boolean save = s

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Mysql8.0修改配置文件my.ini的坑及解决

《Mysql8.0修改配置文件my.ini的坑及解决》使用记事本直接编辑my.ini文件保存后,可能会导致MySQL无法启动,因为MySQL会以ANSI编码读取该文件,解决方法是使用Notepad++... 目录Myhttp://www.chinasem.cnsql8.0修改配置文件my.ini的坑出现的问题

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

MySQL8.0找不到my.ini如何解决

《MySQL8.0找不到my.ini如何解决》在配置MySQL主从复制时,发现找不到my.ini配置文件,通过检查路径和打开隐藏文件夹,最终在C:ProgramDataMySQLMySQLSer... 目录问题描述解决方法总结问题描述今天在配置mysql主从复制的时候发现,找不到my.ini这个配置文件。

Mybatis提示Tag name expected的问题及解决

《Mybatis提示Tagnameexpected的问题及解决》MyBatis是一个开源的Java持久层框架,用于将Java对象与数据库表进行映射,它提供了一种简单、灵活的方式来访问数据库,同时也... 目录概念说明MyBATis特点发现问题解决问题第一种方式第二种方式问题总结概念说明MyBatis(原名

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下