注意:本文中所有源碼分析部分均基于 API23(Android 6.0)
一 事件
| 事件 | 簡介 |
|---|---|
| ACTION_DOWN | 手指 初次接觸到屏幕 時觸發(fā)。 |
| ACTION_MOVE | 手指 在屏幕上滑動 時觸發(fā),會會多次觸發(fā)。 |
| ACTION_UP | 手指 離開屏幕 時觸發(fā)。 |
| ACTION_CANCEL | 事件 被上層攔截 時觸發(fā)。 |
對于單指觸控來說,一次簡單的交互流程是這樣的:
手指落下(ACTION_DOWN) -> 移動(ACTION_MOVE) -> 離開(ACTION_UP)
ACTION_MOVE 有多次觸發(fā)。
如果僅僅是單擊,不會觸發(fā) ACTION_MOVE。
二 事件分發(fā)、攔截與消費(fèi)
下表省略了 PhoneWidow 和 DecorView。
√ 表示有該方法。
X 表示沒有該方法。
| 類型 | 方法 | activity | viewGroup | view |
|---|---|---|---|---|
| 事件分發(fā) | dispatchTouchEvent | √ | √ | √ |
| 事件攔截 | onInterceptTouchEvent | X | √ | X |
| 事件消費(fèi) | onTouchEvent | √ | √ | √ |
1 方法功能介紹
- dispatchTouchEvent 事件分發(fā)
activity,view,viewGroup都擁有該方法 - onInterceptTouchEvent 事件攔截
viewGroup獨(dú)有的方法,負(fù)責(zé)如果viewGroup需要攔截事件,此函數(shù)返回true,交于viewGroup處理 - onTouchEvent
這個三個方法均有一個 boolean(布爾) 類型的返回值,通過返回 true 和 false 來控制事件傳遞的流程。
PS:從上表可以看到 Activity 和 View 都是沒有事件攔截的,這是因?yàn)椋?/p>
- Activity 作為原始的事件分發(fā)者,如果 Activity 攔截了事件會導(dǎo)致整個屏幕都無法響應(yīng)事件,這肯定不是我們想要的效果。
- View最為事件傳遞的最末端,要么消費(fèi)掉事件,要么不處理進(jìn)行回傳,根本沒必要進(jìn)行事件攔截。
2 事件分發(fā)、攔截與消費(fèi)三個方法執(zhí)行調(diào)用順序
viewGroup :分發(fā)->攔截->消費(fèi)
view :分發(fā)->消費(fèi)
Activity :分發(fā)->消費(fèi)
調(diào)用流程

事件調(diào)用流程.jpg
activity 源碼
activity #dispatchTouchEvent源碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 調(diào)用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各個事件的調(diào)用順序
- 單擊事件(onClickListener) 需要兩個兩個事件(ACTION_DOWN 和 ACTION_UP )才能觸發(fā),如果先分配給onClick判斷,等它判斷完,用戶手指已經(jīng)離開屏幕,黃花菜都涼了,定然造成 View 無法響應(yīng)其他事件,應(yīng)該最后調(diào)用。(最后)
- 長按事件(onLongClickListener) 同理,也是需要長時間等待才能出結(jié)果,肯定不能排到前面,但因?yàn)椴恍枰狝CTION_UP,應(yīng)該排在 onClick 前面。(onLongClickListener > onClickListener)
- 觸摸事件(onTouchListener) 如果用戶注冊了觸摸事件,說明用戶要自己處理觸摸事件了,這個應(yīng)該排在最前面。(最前)
- View自身處理(onTouchEvent) 提供了一種默認(rèn)的處理方式,如果用戶已經(jīng)處理好了,也就不需要了,所以應(yīng)該排在 onTouchListener 后面。(onTouchListener > onTouchEvent)
所以事件的調(diào)度順序應(yīng)該是
onTouchListener > onTouchEvent > onLongClickListener > onClickListener。
三 事件分發(fā)流程
事件收集之后最先傳遞給 Activity, 然后依次向下傳遞,大致如下:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
如果view沒有處理事件,再反向交回事件,判斷處理
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
四
五
核心要點(diǎn)
- 事件分發(fā)原理: 責(zé)任鏈模式,事件層層傳遞,直到被消費(fèi)。
- ** View 的 dispatchTouchEvent 主要用于調(diào)度自身的監(jiān)聽器和 onTouchEvent。**
- ** View的事件的調(diào)度順序是onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。**
- ** 不論 View 自身是否注冊點(diǎn)擊事件,只要 View 是可點(diǎn)擊的就會消費(fèi)事件。**
- 事件是否被消費(fèi)由返回值決定,true 表示消費(fèi),false 表示不消費(fèi),與是否使用了事件無關(guān)。
- ViewGroup 中可能有多個 ChildView 時,將事件分配給包含點(diǎn)擊位置的 ChildView。
- ViewGroup 和 ChildView 同時注冊了事件監(jiān)聽器(onClick等),由 ChildView 消費(fèi)。
- 一次觸摸流程中產(chǎn)生事件應(yīng)被同一 View 消費(fèi),全部接收或者全部拒絕。
- 只要接受 ACTION_DOWN 就意味著接受所有的事件,拒絕 ACTION_DOWN 則不會收到后續(xù)內(nèi)容
- 如果當(dāng)前正在處理的事件被上層 View 攔截,會收到一個 ACTION_CANCEL,后續(xù)事件不會再傳遞過來。