自定義View進階《十三》——事件分發(fā)機制詳解

Android 事件分發(fā)機制詳解,在上一篇文章 事件分發(fā)機制原理 中簡要分析了一下事件分發(fā)機制的原理,原理是十分簡單的,一句話就能總結(jié):責(zé)任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發(fā)展,實際運用場景也越來越復(fù)雜,所以想要徹底玩轉(zhuǎn)事件分發(fā)機制還需要一定技巧,本篇事件分發(fā)機制詳解將帶大家了解 …

你以為我接下來要講源碼?
我就不按套路,所有的源碼都是為了適應(yīng)具體的應(yīng)用場景而寫的,只要能夠理解運用場景,理解源碼也就十分簡單了。所以本篇的核心問題是:正確理解在實際場景中事件分發(fā)機制的作用。 會涉及到源碼,但不是主角。

注意:本文中所有源碼分析部分均基于 API23(Android 6.0) 版本,由于安卓系統(tǒng)源碼改變很多,可能與之前版本有所不同,但基本流程都是一致的。

常見事件

既然是事件分發(fā),總要有事件才能分發(fā)吧,所以我們先了解一下常見的幾種事件。

根據(jù)面向?qū)ο笏枷耄录环庋b成 MotionEvent 對象,由于本篇重點不在于此,所以只會涉及到幾個與手指觸摸相關(guān)的常見事件:



對于單指觸控來說,一次簡單的交互流程是這樣的:

手指落下(ACTION_DOWN) -> 移動(ACTION_MOVE) -> 離開(ACTION_UP)

  • 本次事例中 ACTION_MOVE 有多次觸發(fā)。
  • 如果僅僅是單擊(手指按下再抬起),不會觸發(fā) ACTION_MOVE。

事件分發(fā)、攔截與消費

關(guān)于這一部分內(nèi)容,上一篇文章 事件分發(fā)機制原理 已經(jīng)將流程整理的比較清楚了,本文會深入細(xì)節(jié)來研究這些內(nèi)容。之所以分開講,是為了防止大家被細(xì)節(jié)所迷惑而忽略了整體邏輯。

√ 表示有該方法。
X 表示沒有該方法。

View 相關(guān)

dispatchTouchEvent 是事件分發(fā)機制中的核心,所有的事件調(diào)度都?xì)w它管。不過我細(xì)看表格, ViewGroup 有 dispatchTouchEvent 也就算了,畢竟人家有一堆 ChildView 需要管理,但為啥 View 也有?這就引出了我們的第一個疑問。

Q: 為什么 View 會有 dispatchTouchEvent ?
A: 我們知道 View 可以注冊很多事件監(jiān)聽器,例如:單擊事件(onClick)、長按事件(onLongClick)、觸摸事件(onTouch),并且View自身也有 onTouchEvent 方法,那么問題來了,這么多與事件相關(guān)的方法應(yīng)該由誰管理?毋庸置疑就是 dispatchTouchEvent,所以 View 也會有事件分發(fā)。

相信看到這里很多小伙伴會產(chǎn)生第二個疑問,View 有這么多事件監(jiān)聽器,到底哪個先執(zhí)行?

Q: 與 View 事件相關(guān)的各個方法調(diào)用順序是怎樣的?
A: 如果不去看源碼,想一下讓自己設(shè)計會怎樣?

  • 單擊事件(onClickListener) 需要兩個兩個事件-
    (ACTION_DOWN 和 ACTION_UP )才能觸發(fā),如果先分配給onClick判斷,等它判斷完,用戶手指已經(jīng)離開屏幕,黃花菜都涼了,定然造成 View 無法響應(yīng)其他事件,應(yīng)該最后調(diào)用。(最后)
  • 長按事件(onLongClickListener) 同理,也是需要長時間等待才能出結(jié)果,肯定不能排到前面,但因為不需要ACTION_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。



下面我們來看一下實際測試結(jié)果:

手指按下,不移動,稍等片刻再抬起。

[Listener ]: onTouchListener      ACTION_DOWN
[GcsView  ]: onTouchEvent         ACTION_DOWN
[Listener ]: onLongClickListener  
[Listener ]: onTouchListener      ACTION_UP
[GcsView  ]: onTouchEvent         ACTION_UP
[Listener ]: onClickListener 

可以看到,測試結(jié)果也支持我們猜測的結(jié)論,因為長按 onLongClickListener 不需要 ACTION_UP 所以會在 ACTION_DOWN 之后就觸發(fā)。

接下來就看一下源碼是怎么設(shè)計的(省略了大量無關(guān)代碼):

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false; // result 為返回值,主要作用是告訴調(diào)用者事件是否已經(jīng)被消費。
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        /** 
         * 如果設(shè)置了OnTouchListener,并且當(dāng)前 View 可點擊,就調(diào)用監(jiān)聽器的 onTouch 方法,
         * 如果 onTouch 方法返回值為 true,就設(shè)置 result 為 true。
         */
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
      
        /** 
         * 如果 result 為 false,則調(diào)用自身的 onTouchEvent。
         * 如果 onTouchEvent 返回值為 true,則設(shè)置 result 為 true。
         */
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
    return result;
}

如果覺得源碼還是太長,那么用偽代碼實現(xiàn)應(yīng)當(dāng)是這樣的(省略若干安全判斷),簡單粗暴:

public boolean dispatchTouchEvent(MotionEvent event) {
  if (mOnTouchListener.onTouch(this, event)) {
      return true;
  } else if (onTouchEvent(event)) {
      return true;
  }
  return false;
}

正當(dāng)你沉迷在源碼的”精妙”邏輯的時候,你可能沒發(fā)現(xiàn)有兩個東西失蹤了,等回過神來,定睛一看,哎呦媽呀,OnClick 和 OnLongClick 去哪里了?

不要擔(dān)心,OnClick 和 OnLongClick 的具體調(diào)用位置在 onTouchEvent 中,看源碼(同樣省略大量無關(guān)代碼):

public boolean onTouchEvent(MotionEvent event) {
    ...
    final int action = event.getAction();
    // 檢查各種 clickable
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                removeLongPressCallback();  // 移除長按
                ...
                performClick();             // 檢查單擊
                ...
                break;
            case MotionEvent.ACTION_DOWN:
                ...
                checkForLongClick(0);       // 檢測長按
                ...
                break;
            ...
        }
        return true;                        // ??表示事件被消費
    }
    return false;
}

注意上面代碼中存在一個 return true; 并且是只要 View 可點擊就返回 true,就表示事件被消費了。

舉個栗子: I have a RelativeLayout,I have a View,Ugh,RelativeLayout - View

<RelativeLayout
    android:background="#CCC"
    android:id="@+id/layout"
    android:onClick="myClick"
    android:layout_width="200dp"
    android:layout_height="200dp">
    <View
        android:clickable="true"
        android:layout_width="200dp"
        android:layout_height="200dp" />
</RelativeLayout>

現(xiàn)在你有了一個 RelativeLayout - View 你開開心心的為 RelativeLayout 設(shè)置了一個點擊事件myClick,然而你會發(fā)現(xiàn)不論怎么點都不會接收到信息,仔細(xì)一看,發(fā)現(xiàn)內(nèi)部的 View 有一個屬性 android:clickable="true" 正是這個看似不起眼的屬性把事件給消費掉了,由此我們可以得出如下結(jié)論:

  1. 不論 View 自身是否注冊點擊事件,只要 View 是可點擊的就會消費事件。
  2. 事件是否被消費由返回值決定,true 表示消費,false 表示不消費,與是否使用了事件無關(guān)。

關(guān)于 View 的事件分發(fā)先說這么多,下面我們來看一下 ViewGroup 的事件分發(fā)。

ViewGroup 相關(guān)

ViewGroup(通常是各種Layout) 的事件分發(fā)相對來說就要麻煩一些,因為 ViewGroup 不僅要考慮自身,還要考慮各種 ChildView,一旦處理不好就容易引起各種事件沖突,正所謂養(yǎng)兒方知父母難啊。
VIewGroup 的事件分發(fā)流程又是如何的呢?
上一篇文章 事件分發(fā)機制原理 中我們了解到事件是通過ViewGroup一層一層傳遞的,最終傳遞給 View,ViewGroup 要比它的 ChildView 先拿到事件,并且有權(quán)決定是否告訴要告訴 ChildView。在默認(rèn)的情況下 ViewGroup 事件分發(fā)流程是這樣的。

  1. 判斷自身是否需要(詢問 onInterceptTouchEvent 是否攔截),如果需要,調(diào)用自己的 onTouchEvent。
  2. 自身不需要或者不確定,則詢問 ChildView ,一般來說是調(diào)用手指觸摸位置的 ChildView。
  3. 如果子 ChildView 不需要則調(diào)用自身的 onTouchEvent。

用偽代碼應(yīng)該是這樣的:

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean result = false;             // 默認(rèn)狀態(tài)為沒有消費過

    if (!onInterceptTouchEvent(ev)) {   // 如果沒有攔截交給子View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {                      // 如果事件沒有被消費,詢問自身onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;
}

有人看到這里可能會有疑問,我看過源碼,ViewGroup 的 dispatchTouchEvent 可有二百多行呢,你弄這幾行就想忽悠我,別以為我讀書少。

當(dāng)然了,上述源碼是不完善的,還有很多問題是沒有解決的,例如:

  1. ViewGroup 中可能有多個 ChildView,如何判斷應(yīng)該分配給哪一個?
    這個很容易,就是把所有的 ChildView 遍歷一遍,如果手指觸摸的點在 ChildView 區(qū)域內(nèi)就分發(fā)給這個View。

  2. 當(dāng)該點的 ChildView 有重疊時應(yīng)該如何分配?
    當(dāng) ChildView 重疊時,一般會分配給顯示在最上面的 ChildView。
    如何判斷哪個是顯示在最上面的呢?后面加載的一般會覆蓋掉之前的,所以顯示在最上面的是最后加載的。

如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" 
    android:id="@+id/activity_main"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    tools:context="com.gcssloop.viewtest.MainActivity">
    <View
        android:id="@+id/view1"
        android:background="#E4A07B"
        android:layout_width="200dp"
        android:layout_height="200dp"/>
    <View
        android:id="@+id/view2"
        android:layout_margin="100dp"
        android:background="#BDDA66"
        android:layout_width="200dp"
        android:layout_height="200dp"/>
</RelativeLayout>

當(dāng)手指點擊有重疊區(qū)域時,分如下幾種情況:

  1. 只有 View1 可點擊時,事件將會分配給 View1,即使被 View2 遮擋,這一部分仍是 View1 的可點擊區(qū)域。
  2. 只有 View2 可點擊時,事件將會分配給 View2。
  3. View1 和 View2 均可點擊時,事件會分配給后加載的 View2,View2 將事件消費掉,View1接收不到事件。
注意:
  • 上面說的是可點擊,可點擊包括很多種情況,只要你給View注冊了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一個監(jiān)聽器或者設(shè)置了 android:clickable=”true” 就代表這個 View 是可點擊的。
    另外,某些 View 默認(rèn)就是可點擊的,例如,Button,CheckBox 等。
  • 給 View 注冊 OnTouchListener 不會影響 View 的可點擊狀態(tài)。即使給 View 注冊 OnTouchListener ,只要不返回 true 就不會消費事件。

4.ViewGroup 和 ChildView 同時注冊了事件監(jiān)聽器(onClick等),哪個會執(zhí)行?
事件優(yōu)先給 ChildView,會被 ChildView消費掉,ViewGroup 不會響應(yīng)。

5.所有事件都應(yīng)該被同一 View 消費
在上面的例子中我們分析后可以了解到,同一次點擊事件只能被一個 View 消費,這是為什呢?主要是為了防止事件響應(yīng)混亂,如果再一次完整的事件中分別將不同的事件分配給了不同的 View 容易造成事件響應(yīng)混亂。

View 中 onClick 事件需要同時接收到 ACTION_DOWN 和 ACTION_UP 才能觸發(fā),如果分配給了不同的 View,那么 onClick 將無法被正確觸發(fā)。

安卓為了保證所有的事件都是被一個 View 消費的,對第一次的事件( ACTION_DOWN )進行了特殊判斷,View 只有消費了 ACTION_DOWN 事件,才能接收到后續(xù)的事件(可點擊控件會默認(rèn)消費所有事件),并且會將后續(xù)所有事件傳遞過來,不會再傳遞給其他 View,除非上層 View 進行了攔截。
如果上層 View 攔截了當(dāng)前正在處理的事件,會收到一個 ACTION_CANCEL,表示當(dāng)前事件已經(jīng)結(jié)束,后續(xù)事件不會再傳遞過來。

源碼:

其實如果能夠理解上面的內(nèi)容,不看源碼也能非常順利的使用事件分發(fā),但源碼中能挖掘出更多的內(nèi)容。

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 調(diào)試用
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // 判斷事件是否是針對可訪問的焦點視圖(很晚才添加的內(nèi)容,個人猜測和屏幕輔助相關(guān),方便盲人等使用設(shè)備)
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 處理第一次ACTION_DOWN.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 清除之前所有的狀態(tài)
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 檢查是否需要攔截.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);    // 詢問是否攔截
                ev.setAction(action);                       // 恢復(fù)操作,防止被更改
            } else {
                intercepted = false;
            }
        } else {
            // 沒有目標(biāo)來處理該事件,而且也不是一個新的事件事件(ACTION_DOWN), 進行攔截。
            intercepted = true;
        }

        // 判斷事件是否是針對可訪問的焦點視圖
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 檢查事件是否被取消(ACTION_CANCEL).
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        
        // 如果沒有取消也沒有被攔截 (進入事件分發(fā))
        if (!canceled && !intercepted) {

            // 如果事件是針對可訪問性焦點視圖,我們將其提供給具有可訪問性焦點的視圖。
            // 如果它不處理它,我們清除該標(biāo)志并像往常一樣將事件分派給所有的 ChildView。 
            // 我們檢測并避免保持這種狀態(tài),因為這些事非常罕見。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // 清除此指針I(yè)D的早期觸摸目標(biāo),防止不同步。
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);   // 獲取觸摸位置坐標(biāo)
                    final float y = ev.getY(actionIndex);
                    // 查找可以接受事件的 ChildView
                    final ArrayList<View> preorderedList = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    // ▼注意,從最后向前掃描
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);

                        // 如果有一個視圖具有可訪問性焦點,我們希望它首先獲取事件,
                        // 如果不處理,我們將執(zhí)行正常的分派。 
                        // 盡管這可能會分發(fā)兩次,但它能保證在給定的時間內(nèi)更安全的執(zhí)行。
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        // 檢查View是否允許接受事件(即處于顯示狀態(tài)(VISIBLE)或者正在播放動畫)
                        // 檢查觸摸位置是否在View區(qū)域內(nèi)
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        // getTouchTarget 中判斷了 child 是否包含在 mFirstTouchTarget 中
                        // 如果有返回 target,如果沒有返回 null 
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // ChildView 已經(jīng)準(zhǔn)備好接受在其區(qū)域內(nèi)的事件。
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;  // ??已經(jīng)找到目標(biāo)View,跳出循環(huán)
                        }

                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                      
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 沒有找到 ChildView 接收事件
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // 分發(fā) TouchTarget
        if (mFirstTouchTarget == null) {
            // 沒有 TouchTarget,將當(dāng)前 ViewGroup 當(dāng)作普通的 View 處理。
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 分發(fā)TouchTarget,如果我們已經(jīng)分發(fā)過,則避免分配給新的目標(biāo)。 
            // 如有必要,取消分發(fā)。
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // 如果需要,更新指針的觸摸目標(biāo)列表或取消。
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

核心要點

  1. 事件分發(fā)原理: 責(zé)任鏈模式,事件層層傳遞,直到被消費。
  2. View 的 dispatchTouchEvent 主要用于調(diào)度自身的監(jiān)聽器和 onTouchEvent。
  3. View的事件的調(diào)度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
  4. 不論 View 自身是否注冊點擊事件,只要 View 是可點擊的就會消費事件。
  5. 事件是否被消費由返回值決定,true 表示消費,false 表示不消費,與是否使用了事件無關(guān)。
  6. ViewGroup 中可能有多個 ChildView 時,將事件分配給包含點擊位置的 ChildView。
  7. ViewGroup 和 ChildView 同時注冊了事件監(jiān)聽器(onClick等),由 ChildView 消費。
  8. 一次觸摸流程中產(chǎn)生事件應(yīng)被同一 View 消費,全部接收或者全部拒絕。
  9. 只要接受 ACTION_DOWN 就意味著接受所有的事件,拒絕 ACTION_DOWN 則不會收到后續(xù)內(nèi)容。
  10. 如果當(dāng)前正在處理的事件被上層 View 攔截,會收到一個 ACTION_CANCEL,后續(xù)事件不會再傳遞過來。

總結(jié)

本文啰嗦了這么多內(nèi)容,但真正需要注意的就是核心要點中的幾個概念,只要能正確理解這些概念,相信理解事件分發(fā)機制將再也不是難題。

最后,個人推薦閱讀源碼的方法,先嘗試用自己的角度去分析,建立概念,然后看源碼進行驗證、對比,如果發(fā)現(xiàn)自己建立的概念有問題,就嘗試修正自己的概念,這樣比較容易理解原作者的意圖,也不容易被眾多的代碼所迷惑。

就像 ViewGroup 中的 dispatchTouchEvent 內(nèi)容非常多,主要是為了應(yīng)對實際的場景,里面有很多 安全判斷,處理多指觸控 等內(nèi)容,這些如果不先建立概念就去看源碼很容易被這些細(xì)節(jié)問題所迷惑。

參考資料

View
ViewGroup.java
Android Touch事件分發(fā)詳解
基于源碼來了解Android的事件分發(fā)機制

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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