核心概念
-
Event: Down、Move、Up、Cancel -
onTouchEvent(): 處理以上四個事件, 統(tǒng)稱為一個事件流 -
onInterceptTouchEvent: 默認false, 在合適的時機, 返回true -
requestDisallowInterceptTouchEvent
: 子類需要臨時接管事件流的時候, 調(diào)用該方法, 從而處理該事件流 -
dispatchTouchEvent(): 包含onTouchEvent()和onInterceptTouchEvent()
流程
1. View的事件流程
View.onTouchEvent(MotionEvent evetn) {
/*
evetn 包含了事件類型: 按下 抬起 or 其它
以及 坐標 和 其他各種信息
*/
}
整個事件流, 可以分為一下三種情況:
點擊 : Down > Up
滑動 : Down > Move...Move > Up
取消 : Down > Move...Move > Cancel
2. 實現(xiàn)觸摸反饋算法
public class CustomClickableView extends View {
@Override public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// to do something
}
return true; // 攔截: 下一節(jié)有說到
}
}
3. 事件分發(fā)
核心: 離用戶最近的可觸摸的控件, 是這組事件流的響應(yīng)者
如圖: 黃色View 的上層有一個 可點擊的粉色View, 和一個 不可點擊的文本View`

此時, 上層粉色View 是這組事件流的響應(yīng)者

此時, 下層黃色View 是這組事件流的響應(yīng)者
重復(fù)一遍: 離用戶最近的可觸摸的控件, 是這組事件流的響應(yīng)者
在代碼中, 體現(xiàn)在 onTouchEvent(MotionEvent event) 的返回值, 上一節(jié)的代碼塊中, 有提到這個返回值為 true 意味著:
我(View)希望處理以這個 Down 事件為起始點的這條事件流, 請把這之后的后續(xù)事件都交給我吧
4. 事件分發(fā)的攔截機制
截止到目前為止, 所有的邏輯符合直覺:
事件從屏幕最頂部的那個
View向下傳遞
在這個過程前, 還有一個過程:
在用戶觸摸屏幕的時候, 每一個觸摸事件到達
View的onTouchEvent()之前,
Android會從整個Activity里面最底層的那個根View向上一級級地去詢問: "你要不要攔截這組事件?"
攔截的意思就是, 事件我就不交給子View了, 我直接轉(zhuǎn)而自己來處理了
這里就引出了 ViewGroup.onInterceptTouchEvent() 如圖

- 先層底層像上層詢問(
onInterceptTouchEvent): 你是否攔截這組事件- 攔截(true): 走自己的
onTouchEvent()后續(xù)事件不會再詢問 - 不攔截(false): 繼續(xù)向上層詢問
- 攔截(true): 走自己的
- 如果到了最頂層的
ViewGroup都返回了false(不攔截), 此時開始走下一個流程 - 從最頂層的
View.onTouchEvent()開始向下執(zhí)行, 然后就是之前所說的邏輯了
整個邏輯流程像一個 ∩ 形狀: 先向上,再向下
ps: 當 onInterceptTouchEvent 返回 true 時, 會先向子 View 發(fā)送一個 Cancel 事件, 恢復(fù)其之前的狀態(tài)
代碼如下:
public class CustomLayout extends ViewGroup {
@Override public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
...
if (符合條件) {
return true;
}
}
return false;
}
@Override public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
...
}
return true;
}
}
以上說的都是 父View 攔截 子View 的情況, 那如果是 子View 想主動的告知 父View 不要攔截呢?
例如: 列表中, 長按 item 重排功能, 你需要在長按之后的上下滑動, 是移動列表項, 而不是滑動列表
這就引出了 View.requestDisallowInterceptTouchEvent(boolean disallowIntercept) 該方法不需要重寫, 直接調(diào)用, 告知 父View 不要攔截, 我自己處理當前事件流, 僅限當前事件流, 下次使用時需要重新調(diào)用
最后說一句, dispatchTouchEvent() 是事件分發(fā)的總調(diào)度方法, 上面說的 onTouchEvent() 和 onInterceptTouchEvent() 都在發(fā)生在 dispatchTouchEvent() 里面的
所以: 一個事件分發(fā)的過程, 實質(zhì)上就是 從根 View 遞歸地調(diào)用了一次 dispatchTouchEvent() 的過程
其它
本篇內(nèi)容, 總結(jié)自 https://hencoder.com/ui-3-1/