Android事件分发机制以及滑动冲突处理

2024-08-26 06:48

本文主要是介绍Android事件分发机制以及滑动冲突处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载请注明出处:http://blog.csdn.net/u013038616/article/details/50733811

方便日后的查看与交流,将学习与实践总结如下。


一、Android事件传递分析

1、ViewGroup中事件分发机制相关的方法
a、dispatchTouchEvent  事件分发器
b、onInterceptTouchEvent  处理是否拦截事件
c、onTouchEvent 处理对应的事件

ViewGroup中他们的代码关系如下伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev){boolean consume = false;if(onInterceptTouchEvent(ev)){consume = onTouchEvent(ev);}else{consume = child.dispatchTouchEvent(ev);}return consume;}	

常用结论:
a、一个完整的MotionEvent由DOWN(1)->MOVE(n>=0)->UP(1)构成
b、一旦onInterceptTouchEvent拦截了某类事件,该onInterceptTouchEvent方法只会调用一次,后续该事件默认交给该View执行。例如某ViewGroup拦截了DOWN事件,后面的MOVE(n>=0)->UP(1)就默认由该ViewGroup处理,不会向下传递。
c、事件的传递方向:由父容器传向子View,即由外到内。
d、View中没有onInterceptTouchEvent()方法,View默认自己处理事件。
e、ViewGroup的onInterceptTouchEvent()方法默认返回false,即不拦截任何事件。


2、事件分发过程相关类和方法的源码
a、事件传递的大体过程
Activity->Window->View

b、Activity的事件分发器源代码

/*** Called to process touch screen events.  You can override this to* intercept all touch screen events before they are dispatched to the* window.  Be sure to call this implementation for touch screen events* that should be handled normally.** @param ev The touch screen event.** @return boolean Return true if this event was consumed.*/public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}
可以看出Activity先将事件交给Window的superDispatchTouchEvent(ev)处理,如果返回true则事件处理结束,否则事件将最终由Activity的onTouchEvent(ev)方法处理。

c、Window的superDispatchTouchEvent(ev)方法

/*** Used by custom windows, such as Dialog, to pass the touch screen event* further down the view hierarchy. Application developers should* not need to implement or call this.**/public abstract boolean superDispatchTouchEvent(MotionEvent event);
Window是一个抽象类,唯一实现在android.policy.PhoneWindow,Windows的类描述原文如下:

/*** Abstract base class for a top-level window look and behavior policy.  An* instance of this class should be used as the top-level view added to the* window manager. It provides standard UI policies such as a background, title* area, default key processing, etc.** <p>The only existing implementation of this abstract class is* android.policy.PhoneWindow, which you should instantiate when needing a* Window.  Eventually that class will be refactored and a factory method* added for creating Window instances without knowing about a particular* implementation.*/
在android.policy.PhoneWindow中superDispatchTouchEvent方法实现为:

public boolean superDispatchTouchEvent(MotionEvent event){return mDecor.superDispatchTouchEvent(event);}
该方法调用了顶级View(DecorView)的superDispatchTouchEvent(event)方法进行事件分发,顶级View继承自FrameLayout,通过setContentView设置的View为DecorView的子类。
DecorView的类定义如下:
private final class DecorView extends FrameLayout implements RootViewSurfaceView{//...}
因为DecorView也是ViewGroup所以事件分发的过程和ViewGroup的事件分发过程类似。


d、View中dispatchTouchEvent方法主要的分发逻辑

/*** Pass the touch screen motion event down to the target view, or this* view if it is the target.** @param event The motion event to be dispatched.* @return True if the event was handled by the view, false otherwise.*/public boolean dispatchTouchEvent(MotionEvent event) {//......boolean result = false;//......if (onFilterTouchEventForSecurity(event)) {//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;}}//......return result;}
注意:View不是ViewGroup不需要向其子View进行事件分发,所以View中没有onInterceptTouchEvent方法,事件将直接交给OnTouchListener的onTouch或ViewGroup的onTouchEvent方法处理,且OnTouchListener会屏蔽onTouchEvent方法。

e、ViewGroup中dispatchTouchEvent方法主要的分发逻辑
ViewGroup的分发代码比较复杂,暂不列出,有兴趣的话可以去看看源码。


3、一个完整的事件分发过程(该事件的起点区域必须有重叠的子View,父容器才会进行事件分发,否则
父类将自己处理该事件的全部过程)

a、事件先传递给Activity的public boolean dispatchTouchEvent(MotionEvent ev)
b、Activity先 将事件交给Window类的superDispatchTouchEvent(ev)处理
c、Window类再将事件交给顶级View(DecorView)进行事件传递处理
d、顶级View(DecorView)将事件传递给我们定义的布局文件中View(一般为ViewGroup)处理,如果处
理则将事件,即ViewGroup的onInterceptTouchEvent方法返回true,则事件有该ViewGroup处理,如果设
置了setOnTouchEventListener,那么将调用onTouch方法,否则调用其onTouchEvent方法。如果setOnClickListener
点击事件则点击事件被调用;如果不处理该事件,则该事件将向下传递给其子View,调用子View的
dispatchTouchEvent方法,依次处理直到事件被处理,如果最终未处理则进行e。
e、getWindow().superDispatchTouchEvent(ev)返回了false就说明没有View处理该事件,事件将最
终传递给Activity的onTouchEvent进行处理。


二、处理View的滑动冲突

1、滑动冲突的分类
滑动冲突的解决方法是根据上面事件传递的机制进行处理的,熟练掌握事件分发机制是处理滑动冲突的前提。然后就是根据不同的规则来重写View的onInterceptTouchEvent方法,来决定什么时候让父容器拦截滑动事件什么时候让子View拦截滑动事件。

滑动冲突大致可分以下两类:
1、两层滑动嵌套的情况:
a、外层与内层的滑动方向垂直
b、外层与内层的滑动方向平行
2、多层滑动嵌套的情况:可以转化为两层的情况进行处理

滑动冲突的处理方式可分以下两类:
a、外部拦截法:所有事件都要经过父容器,如果父容器需要此事件就进行拦截,不需要就不拦截。
b、内部拦截法:所有事件都传递给子View,如果子View需要此事件就直接消耗,如果不需要就交给
父容器处理。该方法与原有的分发顺序不一样,需要配合parent.requestDisallowInterceptTouchEvent()进行事件重新分发才能正常工作。


2、滑动冲突的实例解决方案
滑动冲突第一类中的第一种的解决方法如下:
如父容器需要左右滑动,而子View需要上下滑动(如ViewPager的情况)。这个解决方法很简单也很典型,直接可以通过用户的滑动来判断用户的意图。通过判断手指在屏幕上移动一小段距离,计算出x轴上的增量dx,y轴上的增量dy,比较dx与dy绝对值的大小,如果dx>dy说明用户想左右滑动;反之,是想上下滑动。这是最直接的一种判断方法,还可以根据滑动方向与水平方向的夹角判断,总之能正确区分用户的意图即可。

a、采用外部拦截法
然后根据上面的规则重写父容器的onInterceptTouchEvent方法,子View不用做任何求改。如下:

// 分别记录上次滑动的坐标private int mLastXIntercept = 0;private int mLastYIntercept = 0;@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {boolean intercepted = false;int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {//这里将返回值设为false是为了让子View可以接受到事件//如果为true则父容器将处理整个事件,子View将接受不到事件intercepted = false;break;}case MotionEvent.ACTION_MOVE: {//根据x轴,y轴方向增量值判断(可以根据不同的规则进行修改)int deltaX = x - mLastXIntercept;int deltaY = y - mLastYIntercept;if (Math.abs(deltaX) > Math.abs(deltaY)) {intercepted = true;} else {intercepted = false;}break;}case MotionEvent.ACTION_UP: {intercepted = false;break;}default:break;}mLastXIntercept = x;mLastYIntercept = y;return intercepted;}

b、采用内部拦截法
该方法需要改变事件的分发顺序,所以需要重写子View的dispatchTouchEvent方法。根据上面的规则子View的dispatchTouchEvent方法改写如下:
注意:其中parent为该View需要拦截滑动事件的那个父容器的引用。

// 分别记录上次滑动的坐标private int mLastX = 0;private int mLastY = 0;@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {//不允许父容器拦截该事件//parent为该View需要拦截滑动事件的那个父容器的引用parent.requestDisallowInterceptTouchEvent(true);break;}case MotionEvent.ACTION_MOVE: {//这里的逻辑可以根据需要进行修改//parent为该View需要拦截滑动事件的那个父容器的引用int deltaX = x - mLastX;int deltaY = y - mLastY;if (Math.abs(deltaX) > Math.abs(deltaY)) {//允许父容器拦截该事件parent.requestDisallowInterceptTouchEvent(false);}break;}case MotionEvent.ACTION_UP: {break;}default:break;}mLastX = x;mLastY = y;return super.dispatchTouchEvent(event);}

父容器也要做相应的修改:
父容器需要拦截处理ACTION_DOWN以外的所有事件。然后通过在子View中调用requestDisallowInterceptTouchEvent方法来控制父容器是否截断事件。

@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {int action = event.getAction();if (action == MotionEvent.ACTION_DOWN) {return false;} else {return true;}}
这里父容器如果拦截ACTION_DOWN事件,那么整个事件将交给父容器处理,子View将接受不到任何事件,从而不能进行内部拦截。

滑动冲突第一类中的第二种的解决方法和第一种的解决方法类似,虽然不能从用户点击屏幕的操作直接判断出用户的意图,但是可以根据不同的业务逻辑加以区分,只要能确定什么时候让父容器截获事件什么时候子View截获事件,就将上面方法的标注部分就行相应的修改即可处理此类冲突。


第二类处理起来比较复杂一些,需要将多层的冲突分解为第一类中单个的事件冲突,对单个的事件冲突再进项逻辑判断上的处理,就可将大化小,一层一层的处理冲突,即可解决此类滑动冲突。
















这篇关于Android事件分发机制以及滑动冲突处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1107849

相关文章

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

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

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

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

java中反射(Reflection)机制举例详解

《java中反射(Reflection)机制举例详解》Java中的反射机制是指Java程序在运行期间可以获取到一个对象的全部信息,:本文主要介绍java中反射(Reflection)机制的相关资料... 目录一、什么是反射?二、反射的用途三、获取Class对象四、Class类型的对象使用场景1五、Class

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Python实现自动化接收与处理手机验证码

《Python实现自动化接收与处理手机验证码》在移动互联网时代,短信验证码已成为身份验证、账号注册等环节的重要安全手段,本文将介绍如何利用Python实现验证码的自动接收,识别与转发,需要的可以参考下... 目录引言一、准备工作1.1 硬件与软件需求1.2 环境配置二、核心功能实现2.1 短信监听与获取2.

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

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

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件