Android事件分发机制

news/2024/11/28 20:35:10/

一、知识前瞻

  • 用户对屏幕的操作的事件可以划分为3种最基础的事件:ACTION_DOWN、ACTION_MOVE、ACTION_UP
  • 事件分发机制分为三部分:事件生产、事件分发 、事件消费

二、事件分发

1.主要方法

  • dispatchTouchEvent:用于进行点击事件的分发
  • onInterceptTouchEvent:用于进行点击事件的拦截,只有 ViewGroup才有
  • onTouchEvent:用于处理点击事件

2.主要流程

当一个点击事件发生时,事件分发从Action_Down开始,首先会将点击事件传递到Activity中,具体是执行Activity的dispatchTouchEvent()进行事件分发,不拦截不中断的正常分发流程:

  1. Activity.dispatchTouchEvent()
  2. Window.superDispatchTouchEvent()
  3. DecorView.superDispatchTouchEvent()
  4. ViewGroup.dispatchTouchEvent()   实现了事件传递 activity -> ViewGroup

在ViewGroup的dispatchTouchEvent方法中调用onInterceptTouchEvent判断是否拦截

  • 若拦截调用ViewGroup的onTouchEvent方法,该ViewGroup消费掉;
  • 若不拦截,该ViewGroup遍历子View,根据点击的位置等条件判断是否有满足接收事件条件的子View?
    • 若有,分发给该子View的dispatchTouchEvent()方法,然后会调用View的onTouchEvent方法,在onTouchEvent方法中会判断该子View是否可点击
      • 是,则事件最终传递到View的onClick方法消费;
        否则,事件返回向上传递,直到消费或者终止。

3.view的onTouchEvent、OnClickListener和onTouchListener的onTouch优先级

  • onTouch:指的是View设置的OnTouchListener接口的onTouch()

    当一个View绑定了OnTouchLister后,当有touch事件触发时,就会调用onTouch方法。

    (当把手放到View上后,onTouch() 被一遍一遍地被调用),如果onTouch返回值为true,表示这个touch事件被onTouch方法处理完毕,不会把touch事件再传递给Activity,否则,touch事件被传递给Activity,onTouchEvent方法被调用

  • onTouchEvent:指的是事件分发中的重要方法,消费(dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent)重写了Activity的onTouchEvent方法后,当屏幕有touch事件时,此方法就会被调用。
  • onClick:指的是View设置的OnClickListener接口的onClick()

优先级从高到低:onTouch > onTouchEvent > onClick

4.ACTION_CANCEL触发时机

子View在处理一个Touch事件中,父View的onInterceptTouchEvent返回true,此时子View会接收到MotionEvent.Action_Cancel。

5.事件是先到DecorView还是先到Window

ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup

这个流程为什么绕来绕去的,光DecorView就走了两遍。

主要原因就是解耦。

ViewRootImpl并不知道有Activity这种东西存在,它只是持有了DecorView。所以先传给了DecorView,而DecorView知道有AC,所以传给了AC。

Activity也不知道有DecorView,它只是持有PhoneWindow,所以这么一段调用链就形成了

6.点击事件被拦截,但是想传到下面的view,如何操作

重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View

7.如何解决view的事件冲突

1.外部滑动方向与内部方向不一致。

比如你用ViewPaper和Fragment搭配,而Fragment里往往是一个竖直滑动的ListView,这种情况是就会产生滑动冲突,但是由于ViewPaper本身已经处理好了滑动冲突,所以我们无需考虑,不过若是换成ScrollView,我们就得自己处理滑动冲突了

处理思路:

主要是一个横向一个竖向的,所以我们只要判断滑动方向是竖向还是横向的,再让对应的View滑动即可。判断的方法有很多,比如竖直距离与横向距离的大小比较;滑动路径与水平形成的夹角等等。

2.外部方向与内部方向一致。

因为内部和外部滑动方向一致,系统会分不清你要滑动哪个部分,所以会要么只有一层能滑动,要么两层一起滑动得很卡顿

处理思路:

对于这种情况,比较特殊,我们没有通用的规则,得根据业务逻辑来得出相应的处理规则。举个最常见的例子,ListView下拉刷新,需要ListView自身滑动,但是当滑动到头部时需要ListView和Header一起滑动,也就是整个父容器的滑动。如果不处理好滑动冲突,就会出现各种意想不到情况。

滑动冲突的处理方法:

  • 让事件都经过父容器的拦截处理,如果父容器需要则拦截,如果不需要则不拦截,称为外部拦截法
public boolean onInterceptTouchEvent(MotionEvent event) {boolean intercepted = false;int x = (int)event.getX();int y = (int)event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {intercepted = false;break;}case MotionEvent.ACTION_MOVE: {if (满足父容器的拦截要求) {intercepted = true;} else {intercepted = false;}break;}case MotionEvent.ACTION_UP: {intercepted = false;break;}default:break;}mLastXIntercept = x;mLastYIntercept = y;return intercepted;
}

首先down事件父容器必须返回false ,因为若是返回true,也就是拦截了down事件,那么后续的move和up事件就都会传递给父容器,子元素就没有机会处理事件了。其次是up事件也返回了false,一是因为up事件对父容器没什么意义,其次是因为若事件是子元素处理的,却没有收到up事件会让子元素的onClick事件无法触发。

  • 父容器不拦截任何事件,将所有事件传递给子元素,如果子元素需要则消耗掉,如果不需要则通过requestDisallowInterceptTouchEvent方法交给父容器处理,称为内部拦截法

首先需要重写子元素的dispatchTouchEvent方法:

  @Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {parent.requestDisallowInterceptTouchEvent(true);break;}case MotionEvent.ACTION_MOVE: {int deltaX = x - mLastX;int deltaY = y - mLastY;if (父容器需要此类点击事件) {parent.requestDisallowInterceptTouchEvent(false);}break;}case MotionEvent.ACTION_UP: {break;}default:break;}mLastX = x;mLastY = y;return super.dispatchTouchEvent(event);}  

然后修改父容器的onInterceptTouchEvent方法:

 @Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {int action = event.getAction();if (action == MotionEvent.ACTION_DOWN) {return false;} else {return true;}}  


http://www.ppmy.cn/news/48771.html

相关文章

Kotlin 用于数据科学的基础库(深度学习、数据挖掘)

Kotlin 用于数据科学 从构建数据流水线到生产机器学习模型, Kotlin 可能是处理数据的绝佳选择: Kotlin 简洁、易读且易于学习。静态类型与空安全有助于创建可靠的、可维护的、易于故障排除的代码。作为一种 JVM 语言,Kotlin 提供了出色的性…

Vue 条件语句

文章目录 Vue 条件语句条件判断v-ifv-elsev-else-ifv-show Vue 条件语句 条件判断 v-if 条件判断使用 v-if 指令&#xff1a; v-if 指令 在元素 和 template 中使用 v-if 指令&#xff1a; <div id"app"><p v-if"seen">现在你看到我了<…

光学仿真小作品集

光学仿真小作品集 传播方向与 z 轴平行的二维平面波自由空间中的传输传播方向与 z 轴有一定夹角的二维平面波自由空间中的传输二维球面波自由空间中的传输------中心位置作为起始点二维球面波自由空间中的传输------角落作为起始点 本文将展示一些作者本人平时自制的光学仿真小…

热血

周五的晚上&#xff0c;决定去看「灌篮高手」电影了。 那还是很多年以前&#xff0c;樱木双手插进裤腰歪头扭嘴吹着口哨&#xff0c;那不羁的样子像极了一只从上往下看的沙雕。 而全国赛的樱木&#xff0c;多少是成熟了很多&#xff0c;是会说一些犯二的话&#xff0c;会和流川…

uniapp的操作流程

目录 步骤1&#xff1a;创建UniApp项目 步骤2&#xff1a;运行UniApp项目 步骤3&#xff1a;编辑UniApp页面 步骤4&#xff1a;添加UniApp插件 步骤5&#xff1a;打包UniApp项目 步骤6&#xff1a;调试和发布UniApp项目 UniApp是一款基于Vue.js的跨平台应用开发框架&…

java中>>>是什么意思?

代码中可能有如下内容。两种写法都是一样的&#xff0c;相同的。 int a 2201;a >>> 2;Log.d("111333","这是页面 ----------打印a"a);int b 2201;int g(b>>>2);Log.d("111333","这是页面 ----------打印g"g);…

销售高品质 FKM EPDM NBR 硅胶 O 形密封圈

O形圈常用于各种行业&#xff0c;包括汽车、航空航天和制造业。它们是由不同材料制成的圆环&#xff0c;用于将两个或多个组件密封在一起。用于制造O形圈的材料是决定其有效性和耐用性的重要因素。在本文中&#xff0c;我们将讨论用于制作O形圈的不同类型的材料。 1.丁腈橡胶(…

【技能分享】CAD转SHP最好的方法

1、利用 ArcToolsbox 工具先将 DWG 文件转为 MDB 通过 CASS 软件生成的 DWG 文件&#xff0c;字段中包含有很多属性内容&#xff0c;所以我们先将 DWG 格式 的文件转换为 MDB 格式&#xff0c;再通过 MDB 转换为 SHP 格式数据进行整理。具体步骤如下&#xff1a; 通过 ArcTool…