注意:本文中所有源码分析部分均基于 API23(Android 6.0)
一 事件
事件 | 简介 |
---|---|
ACTION_DOWN | 手指 初次接触到屏幕 时触发。 |
ACTION_MOVE | 手指 在屏幕上滑动 时触发,会会多次触发。 |
ACTION_UP | 手指 离开屏幕 时触发。 |
ACTION_CANCEL | 事件 被上层拦截 时触发。 |
对于单指触控来说,一次简单的交互流程是这样的:
手指落下(ACTION_DOWN) -> 移动(ACTION_MOVE) -> 离开(ACTION_UP)
ACTION_MOVE 有多次触发。
如果仅仅是单击,不会触发 ACTION_MOVE。
二 事件分发、拦截与消费
下表省略了 PhoneWidow 和 DecorView。
√ 表示有该方法。
X 表示没有该方法。
类型 | 方法 | activity | viewGroup | view |
---|---|---|---|---|
事件分发 | dispatchTouchEvent | √ | √ | √ |
事件拦截 | onInterceptTouchEvent | X | √ | X |
事件消费 | onTouchEvent | √ | √ | √ |
1 方法功能介绍
- dispatchTouchEvent 事件分发
activity,view,viewGroup都拥有该方法 - onInterceptTouchEvent 事件拦截
viewGroup独有的方法,负责如果viewGroup需要拦截事件,此函数返回true,交于viewGroup处理 - onTouchEvent
这个三个方法均有一个 boolean(布尔) 类型的返回值,通过返回 true 和 false 来控制事件传递的流程。
PS:从上表可以看到 Activity 和 View 都是没有事件拦截的,这是因为:
- Activity 作为原始的事件分发者,如果 Activity 拦截了事件会导致整个屏幕都无法响应事件,这肯定不是我们想要的效果。
- View最为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截。
2 事件分发、拦截与消费三个方法执行调用顺序
viewGroup :分发->拦截->消费
view :分发->消费
Activity :分发->消费
调用流程
activity 源码
activity #dispatchTouchEvent源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 调用phoneWindow的superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
activity #onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
2 view各个事件的调用顺序
- 单击事件(onClickListener) 需要两个两个事件(ACTION_DOWN 和 ACTION_UP )才能触发,如果先分配给onClick判断,等它判断完,用户手指已经离开屏幕,黄花菜都凉了,定然造成 View 无法响应其他事件,应该最后调用。(最后)
- 长按事件(onLongClickListener) 同理,也是需要长时间等待才能出结果,肯定不能排到前面,但因为不需要ACTION_UP,应该排在 onClick 前面。(onLongClickListener > onClickListener)
- 触摸事件(onTouchListener) 如果用户注册了触摸事件,说明用户要自己处理触摸事件了,这个应该排在最前面。(最前)
- View自身处理(onTouchEvent) 提供了一种默认的处理方式,如果用户已经处理好了,也就不需要了,所以应该排在 onTouchListener 后面。(onTouchListener > onTouchEvent)
所以事件的调度顺序应该是
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
。
三 事件分发流程
事件收集之后最先传递给 Activity, 然后依次向下传递,大致如下:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
如果view没有处理事件,再反向交回事件,判断处理
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
四
五
核心要点
- 事件分发原理: 责任链模式,事件层层传递,直到被消费。
- ** View 的 dispatchTouchEvent 主要用于调度自身的监听器和 onTouchEvent。**
- ** View的事件的调度顺序是onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。**
- ** 不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。**
- 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
- ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
- ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
- 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
- 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容
- 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。