事件的类型,主要有六种:
- MotionEvent.ACTION_DOWN:第一个手指刚接触屏幕
- MotionEvent.ACTION_POINTER_DOWN:多点触摸第二个手指接触屏幕
- MotionEvent.ACTION_MOVE:手指在屏幕上滑动,如果是多点触摸则无法明确区分具体是哪一个手指在滑动
- MotionEvent.ACTION_POINTER_UP:多点触摸有某个手指离开屏幕,但不是最后一个手指
- MotionEvent.ACTION_UP:最后一个手指离开屏幕
- MotionEvent.CANCEL:非人为因素取消
事件序列
正常情况下,事件序列会由一个DOWN开始、一个UP结尾和0-n个中间事件,比如MOVE、POINTER_DOWN、POINTER_UP组成。
事件分发
事件分发是从Activity --> ViewGroup --> View。
Activity基本不做事,但是可以增加事件序列最开头的响应,和无人消费事件的最末尾响应。
ViewGroup主要职责:
- 在DOWN事件清空上次操作事件的状态位和重置mFirstTouchTarget即事件响应的View链表集合
- 在DOWN事件遍历所有子View询问它们是否要消耗事件,如果有就记录到mFirstTouchTarget集合
- 可以重写onInterceptTouchEvent方法决定是否对事件序列进行拦截,该方法默认是不拦截的
- 分发所有事件,比如MOVE、UP等
- 如果有子View响应了事件,即mFirstTouchTarget有记录,那么事件序列的后续事件直接交给mFirstTouchTarget集合进行处理,不用再遍历子View,所以mFirstTouchTarget的设计理念就是性能优化,去掉了DOWN后续事件的遍历操作。
- 如果没有子View响应事件,那么交由自己的onTouchEvent进行处理,如果自己也不消费事件,那么往上回传,最后可能会传回给Activity;否则,自己消费所有的后续事件序列。
View的职责:
- 判断当前View是ENABLE或DISENABLE
- 判断当前View是可点击或不可点击状态,在可点击状态下,View是默认消费事件的。
- 在DOWN事件设置长按监听,如果超过500毫秒没有UP事件响应,那么长按事件回调。如果这个回调结果代表消费事件,那么不会响应单击事件;否则还可以响应单击事件。
- 在UP事件响应单击事件。会首先响应listener的onTouch(),该方法结果如果代表消费事件,那么onTouchEvent()不会回调;否则可以继续回调onTouchEvent();再看这个方法的结果决定是否回调View.onClickListener的回调。
具体可以看看下面的源码分析
事件分发–Activity
// Activity 这里是事件分发的入口
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 可以重写这个方法实现事件最开始的处理,但是不能拦截事件
onUserInteraction();
}
// 交给下层View去处理,注意window就是PhoneWindow,这里最后会进入ViewGroup的方法,即dispatchTouchEvent(),就是下面会分析的步骤
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果所有的View都不处理事件,那么交给Activity自己处理,虽然实现只是简单的丢弃
return onTouchEvent(ev);
}
事件分发–ViewGroup
// ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
// 记住,事件序列是从DOWN开始
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 所以,这里需要清空之前的事件序列,包括把mFirstTouchTarget和FLAG_DISALLOW_INTERCEPT重置
// mFirstTouchTarget是一个链表,代表要响应这次事件序列的View集合
// FLAG_DISALLOW_INTERCEPT,可以禁止或允许ViewGroup拦截除了DOWN之外的事件,getParent().requestDisallowInterceptTouchEvent()
cancelAndClearTouchTargets(ev);
resetTouchState(