前言
触摸传递机制可以说是Android开发面试高频的一道问题,但总有小伙伴在回答这道问题总不能让面试满意, 本篇就搞清楚面试官问你对触摸传递机制原理时,他最想听到的和其实想问的应该是哪些?下文中,我们将简单剖析一下 Android 的触摸传递机制。
涉及到的类和方法
总的来说,触摸传递过程是由上至下的。一个典型的触摸事件,从 Activity
开始,经过根视图,再经过层层 ViewGroup
,最终传递到某一个 View
或 ViewGroup
上,进行处理。主要涉及到的类自然包括 Activity
,ViewGroup
以及 View
了。
首先,在 Activity
和 View
中,都定义了下面两个方法 (虽然在这两个类中,这两个方法的方法名,参数列表和返回值类型完全一样,但 Activity
并不是 View
的子类,下面的两个方法在 Activity
和 View
中被单独定义)。
// 尝试将触摸事件交给自己的子视图 (如果有的话) 处理: 调用子视图的 dispatchTouchEvent()
// 或者自己处理: 调用自己的 onTouchEvent() 或 OnTouchListener.onTouch()
// 无论是自己的子视图,还是自己,完成了事件处理,都返回 true
public boolean dispatchTouchEvent(MotionEvent ev)
// 尝试自己处理触摸事件. 如果完成处理 (不需要再交给其他 View 处理), 则返回 true
public boolean onTouchEvent(MotionEvent event)
由于 ViewGroup
是 View
的子类,所以自然 ViewGroup
中也存在这两个方法。在 ViewGroup
中还单独定义了方法
// 如果事件需要在该 ViewGroup 截断 (自己处理该事件, 不再传递给其子视图), 则返回 true
public boolean onInterceptTouchEvent(MotionEvent ev)
以上3个方法,通常只有在我们需要自定义 View
时,才需要 Override。对于现成的 View
,我们可以通过 View
(也包括 ViewGroup
) 的 setOnTouchListener()
方法,添加触摸事件监听器来监听触摸事件,
someView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 尝试自己处理触摸事件, 完成处理 (不需要其他 View 再处理), 则返回 true
// ...
}
});
同样可以起到和 onTouchEvent()
类似的效果。两者有什么区别,包括前面的几个方法的具体作用,会在下文中慢慢解释。
在这之前,我们应当注意到,
- 它们都具有一个
MotionEvent
类型的参数,里面包含有触摸事件的详细信息 (包含事件的类型,手指按下还是松开,以及触摸的具体坐标位置等),本文涉及的大部分方法都有这个参数,后面就不再重复了。限于篇幅,这里对该类的使用就不详细介绍了。对MotionEvent
有疑问可以参考官方文档中对 MotionEvent 的描述。 - 它们都返回一个
boolean
值,通过返回true
,来声明触摸事件在自己这里已经完成,或者说“消费”掉了。例如,文章开头的例子中,ListView
的某一项 ( item ) 对应的视图View
监听到一个触摸事件,发现是左右滑动的手势,该View
就会选择将这个事件“消费”掉,这样其父视图ListView
,PageViewer
以及Activity
就不会重复处理这一事件。
传递机制详解
先分别看看上面的4个方法的具体作用,以及 boolean
返回值的意义。
Activity
先看第一个方法 dispatchTouchEvent()
,该方法是整个触摸传递机制的核心。一般地,父视图 ( parent view ) 通过调用子视图 ( child view ) 的 dispatchTouchEvent()
方法完成触摸事件的向下传递。
焦点所在 Activity
的 dispatchTouchEvent()
方法,是整个触摸事件的“入口”。该方法首先,无条件地,不可被截断地 (除非你 Override Activity
的 dispatchTouchEvent()
方法),将事件交给它的下属处理,即调用该 Activity
的根视图的 dispatchTouchEvent()
方法。如果它的下属没有完成该事件的处理 (调用结果返回 false
),则尝试自己处理,即调用 Activity
自己的 onTouchEvent()
方法。如果仍然不能完成处理 (调用结果返回 false
),则可以认为该事件的处理宣告失败,整个方法返回 false
(完成了处理则返回 true
)。
注意,如果第一次调用 (即调用下属视图的 dispatchTouchEvent()
方法),返回了 true
,表示下属已经完成了对事件的处理工作,此时不会再调用 Activity
自己的 onTouchEvent()
方法。
因为 Activity
没有父视图,自身不能设置触摸事件的监听器 OnTouchListener
,也没有 onInterceptTouchEvent()
方法,情况相对简单,就不给大家 show 源代码了。
没有子视图的 View
下面我们再看一下另一个比较简单的情况,即没有子视图的 View
(如果套用二叉树的概念,Activity
是树根&