Android自定义控件 —— 事件分发

摘要

自定义控件在工作中时常有所接触,但始终是只得其形,不得其神。最近抽空系统学习了下这方面的知识,准备用“Android自定义控件”作为一个博客系列来记录我的学习心得。系列文中不乏引用到Android SDK源码,以API 25为准。最后,如有雷同,没错,就是我去抄的。

Android自定义控件系列目录

  • Android自定义控件 —— 事件分发

  • Android自定义控件 —— 三大流程

基础认知

当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象,即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的View去处理,这个事件传递的过程就是分发过程。

为什么要有事件分发机制?安卓上面的View是树形结构的,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,这个点击事件应该给谁呢?为了解决这一个问题,就有了事件分发机制。

View是树形结构的,基于这样的结构,我们的事件可以进行有序的分发。事件收集之后最先传递给Activity, 然后依次向下传递,大致如下

Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View

这样的事件分发机制逻辑非常清晰,可是,如果最后分发到View,而这个View也没有处理事件怎么办,就这样让事件浪费掉?

当然不会,如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后Activity也没有处理,本次事件才会被抛弃

Activity <- PhoneWindow <- DecorView <- ViewGroup <- … <- View

由于PhoneWindow、DecorView和RootView并不是我们需要关心的,所以我们只需要理清Activity、ViewGroup和View之间的事件分发即可。

参与事件分发过程的方法如下

  • public boolean dispatchTouchEvent(MotionEvent ev)

  • public boolean onInterceptTouchEvent(MotionEvent ev)

  • public boolean onTouchEvent(MotionEvent event)

下面用一张图来做个概述

方法作用调用ActivityViewGroupView
dispatchTouchEvent分发(传递)点击事件当点击事件能够传递到当前View时,该方法被调用
onInterceptTouchEvent判断是否拦截了某个事件在dispatchTouchEvent内部调用XX
onTouchEvent处理点击事件在dispatchTouchEvent内部调用

其中,√ 表示有该方法,X 表示没有该方法。

关于和事件分发相关的三个函数的返回值,分为

  • super:调用父类方法

  • true:消费事件,即事件不继续往下传递

  • false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent处理

事件分发流

下面用一张图来说明当点击屏幕进行事件分发时的流程图(即ACTION_DOWN事件)

这里写图片描述

如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。

这里写图片描述

所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是 Activity -> ViewGroup -> View 从上往下调用dispatchTouchEvent(MotionEvent)方法,一直到叶子节点(View)的时候,再由 View -> ViewGroup -> Activity 从下往上调用onTouchEvent(MotionEvent)方法。

dispatchTouchEvent(MotionEvent)onTouchEvent(MotionEvent)一旦return true,事件就停止传递了(到达终点,没有谁能再收到这个事件)。看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。

这里写图片描述

dispatchTouchEvent(MotionEvent)onTouchEvent(MotionEvent)return false,则事件都回传给父控件的onTouchEvent(MotionEvent)处理。

ViewGroup和View对事件的默认实现就是会让整个事件按照U型完整走完,所以return super.xxxxxx()就会让事件依照U型的方向的完整走完整个事件流动路径,中间不做任何改动,不回溯、不终止,每个环节都走到。

这里写图片描述

下面就来说明一下onInterceptTouchEvent(MotionEvent)的作用

这里写图片描述

Intercept的意思就拦截,每个ViewGroup每次在做分发的时候,都要问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)。如果要自己处理,那就在onInterceptTouchEvent(MotionEvent)方法中return true,这样一来就会交给自己的onTouchEvent(MotionEvent)来进行处理;如果不拦截就继续往子控件往下传。

拦截器的默认实现是不会去拦截的,因为子View也需要这个事件。所以onInterceptTouchEvent(MotionEvent)拦截器的默认返回(即return super.onInterceptTouchEvent())和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent(MotionEvent)传递。

上面讲解的都是针对ACTION_DOWN的事件传递,ACTION_MOVE和ACTION_UP在传递的过程中并不是和ACTION_DOWN一样,当在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。

简单的说,就是当dispatchTouchEvent(MotionEvent)在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。

不过可以肯定的是,ACTION_MOVE和ACTION_UP肯定在获取到消费权的函数中可以收到。

参考链接:
1. 图解 Android 事件分发机制
2. 安卓自定义View进阶-事件分发机制原理
3. Android事件分发机制详解:史上最全面、最易懂

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值