Android 触摸系统 - 第一部分:触摸函数和 View 层次结构

在这里插入图片描述
如果对Android视图如何触摸事件没有深刻的理解,你会对许多触摸行为感到迷惑:为什么点击了按钮没有生效?为什么RecyclerView 没有滚动?为什么我需要处理嵌套的ScrollViews

本文将介绍触摸事件如何在 view 层次结构中流动,核心函数如何影响事件流。

背景知识

MotionEvent
在触摸屏上的每次动作都会生成一个 MotionEvent 对象。MotionEvent 类提供了 getter 方法来获取该事件的所有信息,常用的有这些:

  • action (动作的类型)
  • x (相对于处理它的View 的触摸的 x 坐标)
  • y (相对于处理它的View 的触摸的 y 坐标)
  • rawX (相对于设备屏幕的触摸的绝对坐标 x)
  • rawY (相对于设备屏幕的触摸的绝对坐标 y)
  • eventTime (事件发生的时间,基于SystemClock.uptimeMillis())

屏幕坐标
作为提示,Android 屏幕坐标系统用像素为单位测量,以左上角的( x = 0,y = 0)点为原点,到右下角的 (x = MaxX, y = MaxY)点。

动作(Action)
MotionEvent 类的 getAction() 方法返回一个 Int 类型的常量表示动作类型。常见的动作类型有:

  • ACTION_DOWN: 手指或物体开始接触屏幕时。该事件携带了手势的初始位置信息
  • ACTION_UP: 手指或物体离开屏幕时。该事件包含了手势最后离开时的位置信息
  • ACTION_MOVE:在 ACTION_DOWN 到 ACTION_UP 事件之间发生的所有移动事件。手指离开时的位置与起始位置不同
  • ACTION_CANCEL: 当前手势被中止。发生于父 view 拦截了子 view的事件时。例如,当用户手指按在 scroll view 上的一个按钮上并拖动了一段距离,scroll view 会开始滚动,而不是响应按钮的按压事件

手势被定义为一个 MotionEvent 事件序列,起始于ACTION_DOWN,结束于 ACTION_UP 或者 ACTION_CANCEL,中间可能会触发多个 ACTION_MOVE事件。例如,手指在屏幕上做一个拖拽动作后离开,你会触发多个 ACTION_MOVE 事件。

Android 系统处理 MotionEvent 事件
当一个动作发生后,会从顶向下流过 view 层次系统,从view 树的根view(比如 Activity) 开始,如果没被拦截,会一直传递下去,到达事件发生的叶子 view。在从顶向下传播的路径上,view的 dispatchTouchEvent() 被调用, view 可以通过覆写 onInterceptTouchEvent() 方法来拦截事件。如果 onInterceptTouchEvent() 方法返回 true,表示事件被这个 view 消费了,不会再向下传递给子 view 了;如果返回 false,表示 view 知晓这个事件但不消费,继续向下传递。

当事件到达叶子 view 后,或者没一个 view 拦截和消费它,事件会流过返回链直到被消费。在从底向上的传递链上,onTouchEvent() 方法会调用。如果一个 view 的 onTouchEvent() 返回 true,事件就停止传递。如果没有 view 消费事件,事件最终会传到 ActivityonTouchEvent() 方法。所以 Activity 的 onInterceptTouchEvent() 首先被调用,它的 onTouchEvent() 方法最后被调用。

这是当 view B 上有一个触摸事件但没有 view 处理该事件时,事件传递过程中的调用链:
在这里插入图片描述
下面,让我们看看每个函数的细节和它在 ViewViewGroup (View的子类), ScrollView (ViewGroup 的资料)和 Activity 中分别是如何实现的。

dispatchTouchEvent()

View.dispatchTouchEvent
view 没有子 view,所以 dispatchTouchEvent() 实现很简单,该方法调用了 onTouchEvent() ,如果 view 设置了触摸监听,这些触摸监听任何一个返回 true就表示该事件被消费了,方法就会返回 true 。

因为 dispatchTouchEvent() 没有额外的状态管理,立即调用了 onTouchEvent(),所以建议开发者在自定义 view 中覆写 onTouchEvent() 而不是 dispatchTouchEvent(),以避免改变默认的状态管理。

ViewGroup.dispatchTouchEvent
ViewGroup 中,它会调用 onInterceptTouchEvent() ,如果 onInterceptTouchEvent() 返回 false,它会按照添加顺序的倒序来遍历子 view,如果触摸事件在该子view内部,就会调用该子view 的 dispatchTouchEvent() 方法;如果子 view 该方法返回false,表示它不消费该事件, view group 会继续调用下一个子 view 的 dispatchTouchEvent()

建议开发者在自定义 ViewGroup 中覆写 onInterceptTouchEvent() 而不是 dispatchTouchEvent(),因为onInterceptTouchEvent() 方法的目的是监听触摸事件,而在 dispatchTouchEvent() 方法中做附加的状态管理任务。

ScrollView.dispatchTouchEvent
与 ViewGroup 相同,不覆写该方法。

Activity.dispatchTouchEvent
会调用子 view 的 dispatchTouchEvent() 方法。Activity 没有 onInterceptTouchEvent() 方法,所以在自定义的 activity 覆写该方法是保证它消费该事件的唯一方式。否则,任一子 view 的 onTouchEvent() 方法返回 true,activity 的 onTouchEvent() 就不会被调用。

onInterceptTouchEvent()

View.onInterceptTouchEvent
没有该方法。

ViewGroup.onInterceptTouchEvent
不管出于何种目的, 默认实现是返回 false。

覆写该方法的主要目的是让 ViewGroup 处理某种类型的触摸事件, 而子 view 处理另一种类型的触摸事件。例如,ScrollView 覆写该方法来处理滚动事件,其子 view 处理点击之类的事件。

ScrollView.onInterceptTouchEvent
ScrollView 覆写了 ViewGrouponInterceptTouchEvent() 方法。如果是一个 ACTION_MOVE 事件,它会检查事件在支持方向上的速度是否足以被认为是一次拖拽。如果是拖拽, view 会返回 true,其子 view 会收到 ACYTION_CANCEL 事件。该方法还会调用 onInterceptTouchEvent() 这意味着祖先 view 的 onInterceptTouchEvent() 方法被忽略了,滚动优先于任何祖先 view 对触摸事件的处理。

Activity.onInterceptTouchEvent
没有该方法。

onTouchEvent()

View.onTouchEvent
如果 view 可点击,默认实现会返回 true,但除了更新标志位外,没有做其他处理。Android 文档建议在自定义view 中覆写方法时调用 super.onTouchEvent(),因为这里有一些状态处理。如果只想处理点击手势的话,文档建议覆写 performClick() 方法而不是onTouchEvent()

ViewGroup.onTouchEvent
和 View 相同;没有覆写该方法。

ScrollView.onTouchEvent
onTouchEvent() 利用事件信息计算出要滚动的距离,然后进行滚动。它还处理了滚动相关的动画。

Activity.onTouchEvent
默认实现总是返回 false。

总结
见下表格:
在这里插入图片描述

requestDisallowInterceptTouchEvent()

这是 ViewParent 接口的方法,用于子 view不想让其父 view 和其他祖先 view 拦截触摸事件。ViewGroup 实现了 ViewParent 接口。

ViewParent 和它的祖先在该手势事件中必须遵循这个请求,这意味着从 ACTION_DOWN 开始直到 ACTION_UP 或者 ACTION_CANCEL,调用该方法的 view 的祖先的拦截都会被禁止。对于每一个新手势,必须再次调用 requestDisallowInterceptTouchEvent() 方法。

如果不想给 view 的祖先处理事件的机会, view 的 onTouchEvent() 必须返回 true,当事件回流到顶层 view 时祖先的 onTouchEvent() 就不会被触发。

例如,ScrollView 如果检测到滚动行为时,onInterceptTouchEvent()onTouchEvent() 内部就调用了这个方法。

想了解这些函数是如何工作的,请阅读文章《Android触摸系统 - 第二部分:常见事件处理场景》

原文链接:https://proandroiddev.com/android-touch-system-part-1-touch-functions-and-the-view-hierarchy-1f6526e55d78

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值