Android事件分發(fā)的底層原理

在Android中,Touch事件的分發(fā)在WindowManagerService(借助 InputManagerService)負責(zé)采集和分發(fā),在由ViewRootImpl(內(nèi)部有一個mView變量指向View樹的根),負責(zé)控制View樹的UI繪制和事件消息分發(fā)。

當(dāng)輸入設(shè)備可用時,比如觸屏,Linux內(nèi)核在/dev/input/中創(chuàng)建對應(yīng)的設(shè)備節(jié)點。

IMS(inputManagerService),所所做的工作就是監(jiān)聽/dev/input下的所有的設(shè)備節(jié)點,當(dāng)設(shè)備節(jié)點的數(shù)據(jù)時會將數(shù)據(jù)進行加工處理并找到合適的Window(WMS尋找),將輸入事件派發(fā)給他。

事件采集
  • 設(shè)備觸摸
  • 觸摸電信號
  • 電容屏
  • 傳感器
  • 電路板
  • 驅(qū)動
  • Linux
  • IMS(inputManagerService

當(dāng)輸入設(shè)備可用時,比如觸屏,Lunux內(nèi)核會在/dev/input中創(chuàng)建對應(yīng)的設(shè)備節(jié)點,輸入事件所產(chǎn)生的原始信息會被Linux內(nèi)核輸入子系統(tǒng)采集,原始信息由Kernel Space的驅(qū)動層一直傳遞到User Space的接設(shè)備節(jié)點。

事件中轉(zhuǎn)
  • WMS(WindowManagerService

WMS的職責(zé)之一就是輸入系統(tǒng)的中轉(zhuǎn)站,WMS(WindowManagerService)作為WIndow的管理者,會配合IMS將輸入事件交給合適的Window來處理。

事件分發(fā)(分發(fā))
  • ViewRootimpl

ViewRoot中 caiquWindowInputEventReceiver進行具體的事件處理

1:事件分發(fā)的基本概念

  • 觸摸事件:Android的觸摸事件主要封裝在MotionEvent類中
  • 事件類型:包括ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等..
  • 分發(fā)流程:事件從Activity --> ViewGroup --> View的傳遞過程

2:三個核心方法

dispatchTouchEvent(MotionEvent ev)

  • 作用:負責(zé)事件的分發(fā),決定事件是否繼續(xù)傳遞

  • 返回值:true表示消費了事件,false表示不處理此事件

    • true/攔截:調(diào)用自身的onTounchEvent
    • false/不攔截:將事件傳遞給子View
  • 執(zhí)行邏輯:通常先調(diào)用onInterceptTouchEvent判斷是否攔截

onInterceptTouchEvent(MotionEvent ev)

  • 特性:僅ViewGroup擁有此方法(View沒有)

  • 作用:ViewGroup特有方法,用于攔截事件

  • 返回值:true表示攔截事件,false表示不攔截(默認)

    • true/攔截:不再向子View傳遞
    • false/不攔截:默認值
  • 注意:View沒有此方法,無法攔截事件

onTouchEvent(MotionEvent event)

  • 作用:處理點擊事件的具體邏輯
  • 返回值:true表示消費事件,false表示不處理事件
  • 執(zhí)行時機:當(dāng)dispatchTouchEvent傳遞到當(dāng)前View時被調(diào)用
  • 特殊情況:如果沒有子View消費事件,最終會調(diào)用此方法

//事件分發(fā)機制流程

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) { // 判斷是否攔截
        // 攔截后調(diào)用自己的onTouchEvent
        consume = onTouchEvent(ev);
    } else {
        // 不攔截則分發(fā)給子View
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

Activity的事件分發(fā)

  • 入口:事件首先傳遞給Activity的dispatchTouchEvent
  • 傳遞:Activity將事件傳遞給Window,再傳遞給DecorView
  • 最終:事件到達根ViewGroup開始向下分發(fā)

ViewGroup的事件分發(fā)特點

  • 遍歷子View:按Z軸順序遍歷子View檢查能否接受點擊
  • 坐標(biāo)轉(zhuǎn)換:將事件坐標(biāo)轉(zhuǎn)換為子View的相對坐標(biāo)
  • 優(yōu)先處理:如果有子View設(shè)置了TouchListener,會優(yōu)先處理

關(guān)鍵差異對比

函數(shù) ViewGroup View
dispatchTouchEvent 決定是否分發(fā)給子View 直接處理事件
onInterceptTouchEvent 存在,可攔截事件 不存在
onTouchEvent 處理未被分發(fā)的事件 處理自身事件
事件分發(fā)流程
事件流程圖.jpg
  1. Activity → Window → DecorView(ViewGroup)
  2. ViewGroup 調(diào)用 dispatchTouchEvent
  3. ViewGroup 調(diào)用 onInterceptTouchEvent 判斷是否攔截
  4. 如不攔截,則傳遞給相應(yīng)子View
  5. 子View調(diào)用自身的 dispatchTouchEvent 和 onTouchEvent
  6. 如攔截,則調(diào)用自身的 onTouchEvent

事件的內(nèi)部攔截法和外部攔截法

一:外部攔截法(推薦)

基本原理:

  • 在父容器(ViewGroup)的 onInterceptTouchEvent方法中進行攔截控制
  • 通過判斷滑動方向或條件來決定是否攔截事件
// 父容器實現(xiàn)
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        // DOWN事件不能攔截,否則后續(xù)事件都無法接收到
        return false;
    }
    
    // 根據(jù)滑動方向判斷是否攔截
    if (needIntercept()) {
        return true;  // 攔截事件,父容器處理
    }
    return false;  // 不攔截,子View處理
}

private boolean needIntercept() {
    // 根據(jù)滑動距離、方向等條件判斷
    return Math.abs(deltaX) > Math.abs(deltaY);
}

特點:

  • DOWN事件不能攔截:必須返回false,保證父容器能接收到后續(xù)事件
  • 控制簡單:只需在父容器中處理攔截邏輯
  • 符合默認機制:與Android事件分發(fā)機制一致

二:內(nèi)部攔截法

基本原理:

  • 子View通過 requestDisallowInterceptTouchEvent方法控制父容器是否攔截
  • 子View主動告知父容器不要攔截事件
// 子View實現(xiàn)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // 請求父容器不要攔截
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (isScrollToTop()) {
                // 如果滑動到頂部,允許父容器攔截
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            getParent().requestDisallowInterceptTouchEvent(false);
            break;
    }
    return super.dispatchTouchEvent(ev);
}

// 父容器默認不攔截除了DOWN以外的所有事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        return true;  // DOWN事件必須攔截
    }
    return false;   // 其他事件默認不攔截
}

特點:

  • 需要協(xié)調(diào)配合:子View和父容器都需要參與處理
  • 靈活性高:子View可以更精確地控制事件處理
  • 復(fù)雜度較高:需要在多個地方進行處理
注意事項
  • DOWN事件處理:無論哪種方法,ACTION_DOWN 事件都不能被攔截(外部攔截法)或需要特殊處理(內(nèi)部攔截法)
  • 事件完整性:同一事件序列(DOWN-MOVE-UP)應(yīng)由同一個View處理
  • 性能考慮:避免在攔截判斷中進行耗時操作
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容