首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android View 事件分發機制深度解析:從 dispatchTouchEvent 到 onTouchEvent

     編輯:Java 相關知識

前言

Android 觸控事件分發是面試高頻考點,也是自定義 View 必須掌握的核心知識。本文將從源碼角度,系統梳理 ViewGroup 與 View 的事件分發流程,解析 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 三大核心方法的協作機制。

核心流程概覽

事件傳遞遵循「外部攔截、內部消費」原則:

  1. 事件從 Activity.dispatchTouchEvent 開始
  2. 傳遞給根佈局 DecorView (FrameLayout)
  3. ViewGroup 逐層向下分發 (dispatchTouchEvent)
  4. 目標 View 進行處理 (onTouchEvent)
  5. 未消費則向上回傳,觸發父容器 onTouchEvent

View 事件處理詳解

1. dispatchTouchEvent (View)

public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

關鍵邏輯:

  • 優先執行 OnTouchListener.onTouch(),返回 true 表示事件被消費,不再向下傳遞
  • 否則調用自身 onTouchEvent() 繼續處理

2. onTouchEvent (View)

public boolean onTouchEvent(MotionEvent event) {
    if (((viewFlags & CLICKABLE) == CLICKABLE 
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下處理
                break;
            case MotionEvent.ACTION_UP:
                performClick(); // 觸發點擊事件
                break;
            case MotionEvent.ACTION_CANCEL:
                // 取消處理
                break;
            case MotionEvent.ACTION_MOVE:
                // 移動處理
                break;
        }
        return true; // 可點擊/長按視圖消費事件
    }
    return false; // 不可點擊視圖不消費
}

3. performClick 與 OnClickListener

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}

點擊事件最終回調 OnClickListener.onClick(),這就是我們平時設置 setOnClickListener 的觸發點。

ViewGroup 事件分發核心

1. dispatchTouchEvent (ViewGroup) - 簡化版邏輯

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    
    // 1. ACTION_DOWN 時重置狀態
    if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    
    // 2. 判斷是否攔截
    boolean intercepted = false;
    if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        intercepted = !disallowIntercept && onInterceptTouchEvent(ev);
    } else {
        intercepted = true; // 非序列起始且無目標視圖,直接攔截
    }
    
    // 3. 未被攔截,尋找目標子 View
    if (!intercepted) {
        // 遍歷子 View,找到包含觸摸點的 View
        // 呼叫 child.dispatchTouchEvent(transformedEvent)
        // 成功則加入 TouchTarget 列表
    }
    
    // 4. 無子 View 處理,ViewGroup 自身處理
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, false, null, ...);
    }
    
    return handled;
}

2. 關鍵方法解析

onInterceptTouchEvent

ViewGroup 決定是否攔截事件的關鍵:

  • 返回 true:攔截事件,自身處理(調用自身 onTouchEvent)
  • 返回 false:不攔截,繼續分發給子 View
  • 預設返回 false

TouchTarget 機制

ViewGroup 維護 TouchTarget 鏈表,記錄處理特定 Pointer 的子 View:

  • 每個 TouchTarget 對應一個子 View 及其處理的 Pointer ID
  • 32 位 int 存儲 Pointer ID,故最多支援 32 點觸控
  • 子 View dispatchTouchEvent 返回 false 不會加入 TouchTarget,後續事件不再分發給該 View

3. dispatchTransformedTouchEvent

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
    if (child == null) {
        // 無子 View 處理,ViewGroup 自身處理
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        // 座標轉換:從父容器座標系轉換到子 View 座標系
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (!child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        // 關鍵:遞歸調用子 View 的 dispatchTouchEvent
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    return handled;
}

事件分發總結圖解

Activity.dispatchTouchEvent
       ↓
DecorView (ViewGroup).dispatchTouchEvent
       ↓
ViewGroup.onInterceptTouchEvent → true: 自身處理
       ↓ false
遍歷子 View → 找到目標 View
       ↓
子 View.dispatchTouchEvent
       ↓
子 View.onTouchListener.onTouch → true: 消費結束
       ↓ false
子 View.onTouchEvent
       ↓
可點擊 → ACTION_UP → performClick → OnClickListener.onClick
       ↓
返回 true/false 給父 ViewGroup
       ↓
父 ViewGroup 根據返回值決定是否加入 TouchTarget

實戰核心口訣

  1. 下傳:dispatchTouchEvent 向下分發
  2. 攔截:onInterceptTouchEvent 決定是否攔截
  3. 消費:onTouchEvent / onTouch 處理事件
  4. 回傳:返回值決定事件流向(true 消費,false 向上傳遞)

常見問題與解決方案

Q: 為什麼 onTouch 返回 true 會導致 onClick 不觸發?

A: dispatchTouchEvent 中 onTouch 返回 true 時,直接返回 true,不再調用 onTouchEvent,而 performClick 在 onTouchEvent 中觸發。

Q: 如何實現外部攔截法(父容器攔截,子 View 處理)?

A: 父容器 onInterceptTouchEvent 中根據觸摸狀態動態決定是否攔截,配合 requestDisallowInterceptTouchEvent 使用。

總結

Android 事件分發機制通過 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 三大方法協作,實現了靈活的觸控事件路由。理解其核心流程與 TouchTarget 機制,是掌握自定義 View、解決滑動衝突的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved