目录
6.2 相关核心方法返回true或者false,各有什么含义?
6.3 View的enable和clickable的属性值为true或者false有什么区别?
注意:本文只讨论Android Framework已经实现的Touch事件分发过程,暂不讨论工程师对相关类的继承、方法的重写。
1.Touch事件分发--泛而谈之
目前,用户与手机的交互方式以touch(手指触摸屏幕)为主。
用面向对象的思维解释“touch事件分发”,“touch事件”是一个对象,在Android中就是一个MotionEvent类型的实例;“分发”是方法的调用过程,MotionEvent对象作为方法的参数传递给所调用的方法。
MotionEvent对象记录了touch事件类型、发生此touch事件的位置坐标等。
举个例子,当用户触摸手机屏幕上的一个button,然后系统给出一个动态的反馈。从触摸到反馈,泛而谈之,处理流程如下:
2.Touch事件分类
类型 | 描述 |
---|---|
ACTION_DOWN | 手指按下,以下简称down |
ACTION_UP | 手指抬起,以下简称up |
ACTION_MOVE | 手指滑动,,以下简称move |
ACTION_POINTER_DOWN | 多个手指按下 |
ACTION_POINTER_UP | 多个手指抬起 |
ACTION_CANCEL | 取消事件 |
手指从按下,再经过移动,最后抬起,走完一个完整的touch事件流程,即down>move>move>move>move>move>move>up。
手指每触碰到一个位置,都会产生一个MotionEvent对象,并分发下去。
3.参与touch事件分发的视图层级
Activity中有一个PhoneWindow实例,PhoneWindow是抽象类Window的唯一子类。
PhoneWindow中有一个DecorVIew实例,DecorVIew是FrameLayout的子类,本质是一个ViewGroup,DecorVIew是界面的根视图。常见地,在Activity#onCreate()中调用setContentView就是设置DecorVIew的子视图。
touch事件分发的入口是Activity的dispatchTouchEvent(MotionEvent event)方法。
4.参与touch事件分发的核心类和方法
类 | 方法 |
---|---|
Activity | dispatchTouchEvent onTouchEvent |
ViewGroup | dispatchTouchEvent onInterceptTouchEvent onTouchEvent(继承View的) |
View | dispatchTouchEvent onTouchEvent |
View的onTouch方法需要工程师在外部实现,本文对此不做重点讨论。
5.读源码
了解了touch事件分发的基本概念之后,那就开始读源码吧。
读源码秘诀:注重主干,选择性忽略细微的实现;带着问题去读源码比较有针对性,始终记得主线,防止读源码过程中,陷得太深,跑偏了。
读源码目的:理解原理、弥补盲区
6.关于touch事件分发的几个问题
6.1 dispatchTouchEvent方法干了啥
6.1.1 Activity中的dispatchTouchEvent负责分发touch事件给ViewGroup,源码为证:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow().superDispatchTouchEvent(ev),顺着调用线,一直追踪这个方法,可以发现,该方法调用的是DecorVIew对象的dispatchTouchEvent方法。
6.1.2 ViewGroup中的dispatchTouchEvent大体干了三件事:(源码太长,就不全贴了)
6.1.2.1 判断是否要拦截事件,如果不拦截,则将touch事件传递给其子View处理;如果拦截,则将touch事件传递给自己的onTouch或者onTouchEvent方法处理。
6.1.2.2 在当前ViewGroup中找到用户实际触摸到的View。
6.1.2.3 将touch事件分发给View。
6.1.3 View中的dispatchTouchEvent方法处理touch事件,源码为证:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
6.2 相关核心方法返回true或者false,各有什么含义?
方法 | return true | return false |
---|---|---|
Activity # dispatchTouchEvent | 我重写了该方法,打了log,发现该方法返回true和false,执行结果都是:整个touch事件流中的每一个touch事件都会被分发。 | 见左 |
Activity # onTouchEvent | 该方法的返回值就是Activity # dispatchTouchEvent的返回值 | 见左 |
ViewGroup # dispatchTouchEvent | 该ViewGroup消费了touch事件,其父ViewGroup # dispatchTouchEvent也返回true | 该ViewGroup消费不了touch事件,touch事件交由其父ViewGroup来处理,并且不再接收down之后的touch事件 |
ViewGroup # onInterceptTouchEvent | 该ViewGroup拦截了touch事件,该touch事件由自己来处理,不将touch事件分发给子View | 该ViewGroup不拦截touch事件 |
ViewGroup # onTouchEvent | 该方法的返回值就是ViewGroup # dispatchTouchEvent的返回值 | 见左 |
View # dispatchTouchEvent | 该View消费了touch事件,其父ViewGroup # dispatchTouchEvent也返回true | 该View消费不了touch事件,touch事件交由其父ViewGroup来处理,并且不再接收down之后的touch事件 |
View # onTouch | 该方法消费了touch事件,不再执行View # onTouchEvent,View # dispatchTouchEvent也返回true | 该方法消费不了touch事件,将touch事件传递给View # onTouchEvent处理 |
View # onTouchEvent | 该方法的返回值就是View # dispatchTouchEvent的返回值 | 见左 |
可见,touch事件分发过程中,不仅传递MontionEvent对象,还传递返回值。
MontionEvent对象从外往内传
返回值从内往外传
6.3 View的enable和clickable的属性值为true或者false有什么区别?
如果enable为true,且注册了OnTouchListener,才会执行重写后的onTouch方法,这与clickable的值无关
如果执行到了View # onTouchEvent,enable和clickable的值影响onTouchEvent的返回值,关系如下:
enable | clickable | View # onTouchEvent的返回值 |
---|---|---|
false |
true |
true |
false |
false |
false |
true |
true |
true |
true |
false |
false |
6.4 什么时候执行onClick方法
当 注册了View.OnClickListener
&& enable和clickable均为true
&& 执行到了View#onTouchEvent(onTouchEvent中调用了onClick)
&& 一个完整的touch事件流(down>*>up)都传递到了VIew
,才会在up之后执行外部实现的onClick方法。
鸣谢
Android 9.0源码
简书某blog
Android Touch事件分发超详细解析
工匠若水 的CSDN博客, 非常详细的3篇分析文章
Android触摸屏事件派发机制详解与源码分析一(View篇)
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
Android触摸屏事件派发机制详解与源码分析三(Activity篇)