一、事件分發機制核心概念
1. 事件分發三要素
要素 | 作用 | 關鍵方法 |
---|---|---|
事件(Event) | 用戶觸摸動作的封裝 | MotionEvent |
分發者 | 負責將事件傳遞給下級 | dispatchTouchEvent() |
攔截者 | 決定是否截斷事件傳遞(僅ViewGroup) | onInterceptTouchEvent() |
消費者 | 最終處理事件的組件 | onTouchEvent() |
2. 事件序列組成
二、事件分發流程全景圖
1. 事件傳遞層級
2. 核心方法調用鏈
// Activity
public boolean dispatchTouchEvent(MotionEvent ev) {if (getWindow().superDispatchTouchEvent(ev)) {return true; // 事件被消費}return onTouchEvent(ev); // 默認處理
}// PhoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);
}// ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {// 1. 檢查攔截if (onInterceptTouchEvent(ev)) {return onTouchEvent(ev); // 攔截事件}// 2. 分發子Viewfor (View child : children) {if (child.dispatchTouchEvent(ev)) {return true; // 子View消費}}// 3. 自身處理return onTouchEvent(ev);
}// View
public boolean dispatchTouchEvent(MotionEvent event) {if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {return true; // 優先回調OnTouchListener}return onTouchEvent(event); // 默認處理
}
三、ViewGroup 的事件分發機制
1. 攔截決策流程
public boolean onInterceptTouchEvent(MotionEvent ev) {// 默認實現:不攔截return false;
}
2. 分發優先級規則
Z軸順序:后添加的子View優先(可通過
setElevation()
調整)可見性:GONE狀態的View不參與分發
點擊區域:僅分發到觸摸區域內的子View
攔截標志:一旦攔截,整個事件序列不再檢查攔截
3. 事件分發偽代碼
boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = false;// 1. ACTION_DOWN時重置狀態if (action == ACTION_DOWN) {resetTouchState();}// 2. 檢查攔截final boolean intercepted;if (action == ACTION_DOWN || mFirstTouchTarget != null) {intercepted = onInterceptTouchEvent(ev);} else {intercepted = true; // 后續事件默認攔截}// 3. 未攔截時分發子Viewif (!intercepted) {for (View child : reverseChildren) {if (child.isInTouchArea(ev)) {if (child.dispatchTouchEvent(ev)) {mFirstTouchTarget = child; // 記錄消費目標handled = true;break;}}}}// 4. 自身處理if (mFirstTouchTarget == null) {handled = onTouchEvent(ev);}return handled;
}
四、View 的事件處理機制
1. 事件處理優先級
2. onTouchEvent 核心邏輯
public boolean onTouchEvent(MotionEvent event) {// 1. 檢查是否可用if (!isEnabled()) {return clickable; // 不可用時仍返回clickable狀態}// 2. 處理不同事件類型switch (event.getAction()) {case MotionEvent.ACTION_DOWN:setPressed(true); // 設置按壓狀態break;case MotionEvent.ACTION_MOVE:if (!pointInView(event)) {removeTapCallback(); // 移出視圖時取消點擊}break;case MotionEvent.ACTION_UP:if (mHasPerformedLongPress) {break; // 長按已處理}performClick(); // 執行點擊break;case MotionEvent.ACTION_CANCEL:setPressed(false); // 重置狀態break;}return true; // 始終消費事件(如果可點擊)
}
五、事件分發的核心規則
1. 事件序列連續性原則
消費權綁定:消費ACTION_DOWN的View將接收整個事件序列
攔截時機:
ACTION_DOWN:可自由決定是否攔截
后續事件:若未攔截DOWN,仍可攔截MOVE/UP
狀態一致性:View應在DOWN時初始化觸摸狀態
2. 返回值含義表
方法 | 返回true | 返回false |
---|---|---|
dispatchTouchEvent() | 事件已消費 | 事件未消費,繼續傳遞 |
onInterceptTouchEvent() | 攔截事件,不再傳遞子View | 不攔截,繼續傳遞子View |
onTouchEvent() | 事件已處理 | 事件未處理,回傳給父View |
六、滑動沖突解決方案
1. 沖突類型分類
類型 | 示例場景 | 解決方案 |
---|---|---|
同方向沖突 | ScrollView嵌套ListView | 外部攔截法 |
不同方向沖突 | ViewPager內嵌橫向RecyclerView | 內部攔截法 |
嵌套沖突 | 多層嵌套的復雜布局 | 定制分發策略 |
2. 外部攔截法(推薦)
public class ParentView extends ViewGroup {private float mLastX, mLastY;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercepted = false;float x = ev.getX();float y = ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:intercepted = false; // DOWN必須不攔截break;case MotionEvent.ACTION_MOVE:float dx = Math.abs(x - mLastX);float dy = Math.abs(y - mLastY);if (dx > dy && dx > touchSlop) {intercepted = true; // 橫向滑動時攔截}break;case MotionEvent.ACTION_UP:intercepted = false;break;}mLastX = x;mLastY = y;return intercepted;}
}
3. 內部攔截法
public class ChildView extends View {@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {float x = event.getX();float y = event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:getParent().requestDisallowInterceptTouchEvent(true); // 禁止父容器攔截break;case MotionEvent.ACTION_MOVE:if (needParentIntercept()) {getParent().requestDisallowInterceptTouchEvent(false); // 允許父容器攔截}break;}return super.dispatchTouchEvent(event);}
}
七、核心要點
1. 高頻問題清單
事件分發流程是怎樣的?
答:Activity -> Window -> DecorView -> ViewGroup -> View
每個層級通過dispatchTouchEvent()向下傳遞
onTouch和onTouchEvent的區別?
onTouch是View.OnTouchListener接口方法
onTouchEvent是View自身的處理方法
onTouch優先級高于onTouchEvent
ACTION_CANCEL何時觸發?
當父容器攔截事件時發送
用于重置View的觸摸狀態
如何解決滑動沖突?
外部攔截法:重寫父容器onInterceptTouchEvent()
內部攔截法:子View調用requestDisallowInterceptTouchEvent()
為什么ACTION_DOWN特殊處理?
它決定整個事件序列的接收者
父容器在DOWN時必須給子View機會
2. 高級問題解析
Q:requestDisallowInterceptTouchEvent()原理?
// View.java
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept); // 遞歸向上}
}// ViewGroup.java
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT; // 設置標志位if (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}
}// 在ViewGroup的dispatchTouchEvent中
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev); // 檢查攔截
} else {intercepted = false; // 被子View禁止攔截
}
Q:事件分發中的設計模式?
責任鏈模式:事件沿視圖樹傳遞,直到被處理
模板方法模式:dispatchTouchEvent()定義處理框架
觀察者模式:OnTouchListener回調機制
Q:如何優化事件處理性能?
避免在事件方法中創建對象
使用
getActionMasked()
替代getAction()
對復雜手勢使用GestureDetector
減少不必要的觸摸狀態更新