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

相关文章

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk