概述
如果对Android视图如何触摸事件没有深刻的理解,你会对许多触摸行为感到迷惑:为什么点击了按钮没有生效?为什么RecyclerView
没有滚动?为什么我需要处理嵌套的ScrollViews
?
本文将介绍触摸事件如何在 view 层次结构中流动,核心函数如何影响事件流。
目录
- 背景知识
- dispatchTouchEvent()
- onInterceptTouchEvent()
- onTouchEvent()
- requestDisallowInterceptTouchEvent()
背景知识
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 消费事件,事件最终会传到 Activity
的 onTouchEvent()
方法。所以 Activity 的 onInterceptTouchEvent()
首先被调用,它的 onTouchEvent()
方法最后被调用。
这是当 view B 上有一个触摸事件但没有 view 处理该事件时,事件传递过程中的调用链:
下面,让我们看看每个函数的细节和它在 View
、ViewGroup
(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
覆写了 ViewGroup
的 onInterceptTouchEvent()
方法。如果是一个 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
最后
以上就是外向白云为你收集整理的Android 触摸系统 - 第一部分:触摸函数和 View 层次结构背景知识dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()requestDisallowInterceptTouchEvent()的全部内容,希望文章能够帮你解决Android 触摸系统 - 第一部分:触摸函数和 View 层次结构背景知识dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()requestDisallowInterceptTouchEvent()所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复