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

相关文章

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

springboot报错Invalid bound statement (not found)的解决

《springboot报错Invalidboundstatement(notfound)的解决》本文主要介绍了springboot报错Invalidboundstatement(not... 目录一. 问题描述二.解决问题三. 添加配置项 四.其他的解决方案4.1 Mapper 接口与 XML 文件不匹配

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

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

Python中ModuleNotFoundError: No module named ‘timm’的错误解决

《Python中ModuleNotFoundError:Nomodulenamed‘timm’的错误解决》本文主要介绍了Python中ModuleNotFoundError:Nomodulen... 目录一、引言二、错误原因分析三、解决办法1.安装timm模块2. 检查python环境3. 解决安装路径问题

如何解决mysql出现Incorrect string value for column ‘表项‘ at row 1错误问题

《如何解决mysql出现Incorrectstringvalueforcolumn‘表项‘atrow1错误问题》:本文主要介绍如何解决mysql出现Incorrectstringv... 目录mysql出现Incorrect string value for column ‘表项‘ at row 1错误报错

如何解决Spring MVC中响应乱码问题

《如何解决SpringMVC中响应乱码问题》:本文主要介绍如何解决SpringMVC中响应乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC最新响应中乱码解决方式以前的解决办法这是比较通用的一种方法总结Spring MVC最新响应中乱码解

Java报NoClassDefFoundError异常的原因及解决

《Java报NoClassDefFoundError异常的原因及解决》在Java开发过程中,java.lang.NoClassDefFoundError是一个令人头疼的运行时错误,本文将深入探讨这一问... 目录一、问题分析二、报错原因三、解决思路四、常见场景及原因五、深入解决思路六、预http://www

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

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