View事件分發相關結論的源碼解析

View事件分發相關結論的源碼解析

瞭解過View事件分發源碼的同窗或多或少都知道一些事件分發的相關結論,好比某個View若是攔截了事件,那麼它的onTouchEventIntercept方法就不會再次調用,好比事件若是被某個View消耗掉,那麼該序列中的剩餘事件都將交給該View處理等等。android

View事件分發的三個核心方法有三個,分別是dispatchTouchEvent方法,onInterceptTouchEvent方法和onInterceptTouchEvent方法。bash

dispatchTouchEvent方法主要用來進行事件的分發。若是事件可以傳遞給當前View,那麼此方法必定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。ide

onInterceptTouchEvent方法在dispatchTouchEvent方法內部調用,用來判斷是否攔截某個事件,返回結果表示是否攔截當前事件。佈局

onTouchEvent方法也在dispatchTouchEvent方法中調用,返回結果表示是否消耗當前事件。ui

View事件分發相關結論

今天打算針對其中兩個最有用、最難記和最難理解的結論,從源碼層面給出它們的解釋,結論以下:this

結論1、某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用。spa

就是說當一個ViewGroup決定攔截一個事件後,那麼系統會把同一個事件序列內的其餘方法都直接交給它來處理,所以就再也不調用這個View的onInterceptTouchEvent去詢問它是否要攔截了。rest

結論2、某個View一旦開始處理事件,若是它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其餘事件都不會再交給它來處理,而且事件將從新交由它的父元素去處理,即父元素的onTouchEvent會被調用。code

View事件分發其餘的結論,本文這裏暫不作分析,讀者可自行了解。orm

使用例子說明

示例一:不作邏輯修改,只打印log

咱們自定義三個View,分別繼承自RelativeLayout、LinearLayout、TextView,分別重寫他們的dispatchTouchEvent方法,onInterceptTouchEvent方法(這個方法是ViewGroup特有的,View沒有)和onInterceptTouchEvent方法。

在這三個方法內部,不作任何邏輯修改,只依次打印ACTION_DOWN、ACTION_MOVE、ACTION_UP的log。具體代碼以下所示:

public class MyLinearLayout extends LinearLayout {
    private static final String TAG = MyLinearLayout.class.getSimpleName();

    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG + " dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG + " dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG + " dispatchTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG + " onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG + " onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG + " onTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}
複製代碼

MyRelativeLayout的代碼和MyLinearLayout徹底一致,MyTextView稍有不一樣,由於它沒有onInterceptTouchEvent方法。

接着在佈局中使用這三個自定義View,最外側是MyRelativeLayout,子View是一個MyLinearLayout,再內部是MyTextView。具體代碼以下所示:

<com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyRelativeLayout
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="#BCEAC1"
            android:contentDescription="MyLinearLayout">

            <com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyLinearLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:layout_centerInParent="true"
                android:background="#2DC98F"
                android:contentDescription="MyLinearLayout"
                android:orientation="vertical">

                <com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyTextView
                    android:layout_width="200dp"
                    android:layout_height="100dp"
                    android:layout_gravity="center"
                    android:layout_marginTop="50dp"
                    android:background="#839885"
                    android:gravity="center"
                    android:text="MyTextView" />

            </com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyLinearLayout>

        </com.tiny.demo.firstlinecode.view.dispatchevent.summary.MyRelativeLayout>
複製代碼

效果以下圖:

而後咱們點擊MyTextView區域,輸出log以下:

MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyTextView dispatchTouchEvent ACTION_DOWN
MyTextView onTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout onTouchEvent ACTION_DOWN
複製代碼

能夠看出,在不作任何處理的狀況下,事件是從父View依次向子View傳遞的。會先調用父View的dispatchTouchEvent方法和onInterceptTouchEvent方法,接着調用子View的dispatchTouchEvent方法和onInterceptTouchEvent方法,依次遞歸,直到到達最底層View。

達到最底層View後,會調用最底層View的dispatchTouchEvent方法和onTouchEvent方法,接着事件又依次向上傳遞,直到被消費掉,若是沒有被消費,則會傳遞給最外層View。

在咱們這個例子中,具體傳遞就以下圖所示:

須要說明的是,咱們這個例子中只打印了ACTION_DOWN的相關log,沒有打印ACTION_MOVEACTION_UP的,不會由於我偷懶省略,而是由於在事件傳遞過程當中沒有View攔截ACTION_DOWN 事件,也沒有View消費ACTION_DOWN事件,則同一事件序列中的其餘事件都不會傳遞給這些View來進行處理了,都會交由父View的onTouchEvent方法進行處理。

這個例子很好的說明了結論二,至於具體源碼咱們後續再看。

示例二:修改MyLinearLayout,讓其攔截並消耗事件。

咱們回顧下結論一,某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用

因此咱們在示例一的基礎上,修改MyLinearLayout類,讓其攔截並消耗ACTINON_DOWN事件。

具體來講就是在MyLinearrLayout#onInterceptTouchEvent中,當MotionEvent爲ACTION_DOWN時,就返回true;在MyLinearLayout#onTouchEvent方法返回true。具體代碼以下所示:

public class MyLinearLayout extends LinearLayout {
    ...

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_DOWN");
                return true;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG + " onInterceptTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                LogUtils.e(TAG + " onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtils.e(TAG + " onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e(TAG + " onTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return true;
    }
}
複製代碼

佈局不變,跟上個例子一致,而後咱們點擊MyTextView區域,輸出log以下:

MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout dispatchTouchEvent ACTION_MOVE
MyRelativeLayout onInterceptTouchEvent ACTION_MOVE
MyLinearLayout dispatchTouchEvent ACTION_MOVE
MyLinearLayout onTouchEvent ACTION_MOVE
MyRelativeLayout dispatchTouchEvent ACTION_UP
MyRelativeLayout onInterceptTouchEvent ACTION_UP
MyLinearLayout dispatchTouchEvent ACTION_UP
MyLinearLayout onTouchEvent ACTION_UP
複製代碼

能夠看出事件沒有傳遞給子View MyTextView,而是被MyLinearLayout#onInterceptTouchEvent方法攔截,接着被MyLinearLayout#onTouchEvent方法消費了。

接着同一時間序列中剩下的ACTION_MOVEACTION_UP事件傳遞給了MyLinearLayout,也由其消費掉了。

另外,MyLinearLayout#onInterceptTouchEvent只被調用了一次,後續的ACTION_MOVEACTION_UP事件到來時均沒有再次調用onInterceptTouchEvent方法。

這個例子完美的展現了咱們結論一的現象,具體過程以下圖所示:

相關源碼解讀

例子演示完畢,接下來咱們從源碼角度對這兩個結論進行解析。

咱們知道,ViewGroup的onInterceptTouchEvent方法默認返回false,也就是不攔截。ViewGroup中沒有重寫onTouchEvent方法,它使用的是View中的onTouchEvent方法。

經過查看經常使用的LinearLayout、RealtiveLayout、FrameLayout的源碼能夠發現,它們均沒有重寫dispatchTouchEventonInterceptTouchEventonTouchEvent這三個方法,也就是說他們中的事件分發都是遵循ViewGroup中的規則。

源碼基於android-28。

結論一解讀

爲了簡單起見,咱們分段解讀ViewGroup#dispatchTouchEvent方法源碼代碼。

跟事件序列保持一致,咱們依次分析MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP事件。

MotionEvent.ACTION_DOWN流程

爲了方便閱讀,咱們將核心代碼分爲了四部分:

part1:

// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
複製代碼

上述這兩個方法內部都會調用clearTouchTargets方法,該方法會將mFirstTouchTarget置爲null。

何爲mFirstTouchTarget? 當子View成功處理事件時,mFirstTouchTarget會被賦值並指向子元素。

接下來看下是否攔截的邏輯,這裏調用了onInterceptTouchEvent方法。

part2:

// Check for interception.
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); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}
複製代碼

先看外層的if判斷,當是ACTION_DOWN事件或者mFirstTouchTarget不爲空時,就會走if內部邏輯,不然就給intercepted賦值爲true,表示攔截事件。咱們此次是ACTION_DOWN事件,因此就會走if內部邏輯。

咱們再看if爲true時的內部邏輯: 先判斷是否設置了FLAG_DISALLOW_INTERCEPT標記位,默認不設置。該標記位經過requestDisallowInterceptTouchEvent方法設置,這跟咱們此次分析關係不大,暫且不關注。

disallowIntercept的值默認爲false,因此就走調用子if內部的邏輯,會調用咱們的onInterceptTouchEvent方法,該方法默認返回值爲false,表示不攔截。

而咱們重寫了MyLinearLayout的onInterceptTouchEvent方法,因此咱們最終的intercepted的值爲true。

接着往下看part3:

// 咱們intercepted的值爲true,因此不會走下面的if內部邏輯。
if (!canceled && !intercepted) {
    ...
    
    // 若是是ACTION_DOWN事件
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ...
        final int childrenCount = mChildrenCount;
        
        if (newTouchTarget == null && childrenCount != 0) {
            ...
            final View[] children = mChildren;
            
            // 開啓for循環,依次遍歷全部子View
            for (int i = childrenCount - 1; i >= 0; i--) {
                ...
                
                // 調用dispatchTransformedTouchEvent方法進行事件分發。
                // dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ...
                    
                    // 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ...
            }
            ...
        }
        ...
    }
}
複製代碼

此時intercepted的值爲true,因此不會走下面的if內部邏輯。也就不會給mFirstTouchTarget賦值,mFirstTouchTarget的值依然爲null。

接着看,part4:

// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    ...
}
複製代碼

這裏mFirstTouchTarget爲null,因此會調用dispatchTransformedTouchEvent方法,且child參數值爲null。

咱們這裏看下ViewGroup#dispatchTransformedTouchEvent方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    ...

    // Perform any necessary transformations and dispatch.
    // 判斷child參數是否爲空
    if (child == null) {
        // 調用View#dispatchTouchEvent方法。
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...
        // 將事件傳遞給子View。
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    ...
    return handled;
}
複製代碼

上述代碼能夠看出,在dispatchTransformedTouchEvent方法內部,若是child不爲空,就會調用child.dispatchTouchEvent方法,將事件傳遞給child。

若是child參數爲空,就調用super.dispatchTouchEvent(transformedEvent)方法,也就是View#dispatchTouchEvent方法進行事件分發,在其內部就會調用當前ViewGroup的onTouchEvent方法進行事件處理。

咱們這裏Child爲null,因此會調用View#dispatchTouchEvent方法,在其內部會調用onTouchEvent方法。由於咱們重寫了MyLinearLayout#onTouchEvent方法,讓其返回值爲true,表示事件被消費掉,因此事件就不會再向上傳遞了。

上面分析過,dispatchTransformedTouchEvent方法中的child參數若是爲null的話,就會調用當前View#dispatchTouchEvent方法。

咱們看下View的dispatchTouchEvent方法,源碼以下:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;

    ...

    if (onFilterTouchEventForSecurity(event)) {
        ...
        //noinspection SimplifiableIfStatement
        
        // 若是設置了OnTouchListener,就調用mOnTouchListener#onTouch方法。
        // 若是mOnTouchListener#onTouch方法返回值爲true,則result的值爲true。
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        // 若是result爲false,則調用onTouchEvent方法。
        // 若是onTouchEvent方法返回值爲true,則result賦值爲true,表示事件被處理。
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    ...

    return result;
}
複製代碼

咱們沒有給MyLinearLayout設置OnTouchListener,因此MyLinearLayout的onTouchEvent方法會執行。

經過上面的分析,咱們能夠解釋ACTION_DOWN的相關log了:

MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
複製代碼

事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法(child不爲空),將事件分發給子View MyLinearLayout。

在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着將MyLinearLayout#onInterceptTouchEvent方法的返回值(true)賦值給intercepted,因爲這裏進行了攔截,因此事件不會分發給子View(part3部分),因此mFirstTouchTarget也爲null,因此會調用dispatchTransformedTouchEvent方法(child參數值爲null, part4部分),接着調用MyLinearLayout#onTouchEvent方法,該方法也被重寫了,返回值爲true。

因爲MyLinearLayout#onTouchEvent方法返回true,因此MyLinearLayout#dispatchTouchEvent方法也就返回true,因此在MyRelativeLayout#dispatchTouchEvent方法中,將事件分發給子View時(part3部分),dispatchTransformedTouchEvent方法的返回值爲true,因此mFirstTouchEvent的值不爲null,因此事件不會再次向上傳遞給MyRelativeLayout了。

作下總結,在ACTION_DOWN事件被MyLinearLayout處理完以後,mFirstTouchTarget的值爲null。

MotionEvent.ACTION_MOVE流程

ACTION_MOVE事件到來時,會依舊走一遍dispatchTouchEvent方法。咱們依舊按照part一、part二、part三、part4的順序來看。

part1:

// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
複製代碼

此次傳遞的事ACTION_MOVE事件,因此這個邏輯不會走.

part2:

// Check for interception.
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); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}
複製代碼

這裏咱們是ACTION_MOVE事件,且mFirstTouchTarget爲null,因此直接走的是外層if的else邏輯,即intercepted = true,因此這裏不會調用onInterceptTouchEvent方法。

接着往下看,part3:

// 此時intercepted的值爲true,因此不會走下面的if內部邏輯。
if (!canceled && !intercepted) {
    ...
    
    // 若是是ACTION_DOWN事件
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ...
        final int childrenCount = mChildrenCount;
        
        if (newTouchTarget == null && childrenCount != 0) {
            ...
            final View[] children = mChildren;
            
            // 開啓for循環,依次遍歷全部子View
            for (int i = childrenCount - 1; i >= 0; i--) {
                ...
                
                // 調用dispatchTransformedTouchEvent方法進行事件分發。
                // dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ...
                    
                    // 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ...
            }
            ...
        }
        ...
    }
}
複製代碼

此時intercepted的值爲true,因此不會走下面的if內部邏輯。

接着看,part4:

// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    ...
}
複製代碼

這裏會調用onTouchEvent方法,將其返回值賦給handled。

咱們分析下ACTION_MOVE相關log:

MyRelativeLayout dispatchTouchEvent ACTION_MOVE
MyRelativeLayout onInterceptTouchEvent ACTION_MOVE
MyLinearLayout dispatchTouchEvent ACTION_MOVE
MyLinearLayout onTouchEvent ACTION_MOVE
複製代碼

事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法(child不爲空),將事件分發給子View MyLinearLayout。

在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着因爲不知足part2中if判斷的條件,因此intercepted的值爲true,也就沒有調用MyLinearLayout#onInterceptTouchEvent方法。

因爲intercepted值爲true,因此不會走part3的邏輯,因此mFirstTouchTarget的值爲null。

接着因爲mFirstTouchTarget值爲null,因此在part4中會調用MyLinearLayout#dispatchTransformedTouchEvent方法(child爲null),將事件傳遞給MyLinearLayout#onTouchEvent方法。

因爲咱們重寫了MyLinearLayout#onTouchEvent方法,讓其返回值爲true,因此MyLinearLayout#dispatchTouchEvent方法也就返回true,因此在MyRelativeLayout#dispatchTouchEvent方法中,將事件分發給子View時,dispatchTransformedTouchEvent方法的返回值爲true,因此MyRelativeLayout#mFirstTouchEvent的值不爲null,因此事件不會再次向上傳遞給MyRelativeLayout了。

MotionEvent.ACTION_UP流程

ACTION_UP流程與ACTION_MOVE流程徹底一致,這裏再也不贅述。

經過以上分析,咱們從源碼角度解釋告終論一:某個ViewGroup一旦決定攔截(ACTINON_DOWN: onInterceptTouchEvent的返回值爲true),那麼這一個事件序列都只能由它來處理(若是事件序列可以傳遞給它的話),而且它的onInterceptTouchEvent不會再被調用

結論二解讀

這裏咱們也按照MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP事件的順序分析。

MotionEvent.ACTION_DOWN流程

part1:

// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
複製代碼

當前爲ACTION_DOWN事件,因此會走if內部邏輯,會依次調用cancelAndClearTouchTargetsresetTouchState方法。

上述這兩個方法內部都會調用clearTouchTargets方法,該方法會將mFirstTouchTarget置爲null。

接下來看part2:

// Check for interception.
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); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}
複製代碼

先看外層的if判斷,咱們此次是ACTION_DOWN事件,因此就會走if內部邏輯。

咱們再看if爲true時的內部邏輯: 咱們disallowIntercept的值默認爲false,因此就會調用子if內部的邏輯,會調用咱們的onInterceptTouchEvent方法,該方法默認返回值爲false,表示不攔截。

因此咱們最終的intercepted的值爲false。

接着往下看,part3:

// 咱們intercepted的值我爲false,因此會走下面的if內部邏輯。
if (!canceled && !intercepted) {
    ...
    
    // 咱們是ACTION_DOWN事件,因此下面的條件也知足
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ...
        final int childrenCount = mChildrenCount;
        
        // 咱們的MyLinearLayout是有子View的,因此下面的條件也知足
        if (newTouchTarget == null && childrenCount != 0) {
            ...
            final View[] children = mChildren;
            
            // 開啓for循環,依次遍歷全部子View
            for (int i = childrenCount - 1; i >= 0; i--) {
                ...
                
                // 調用dispatchTransformedTouchEvent方法進行事件分發。
                // dispatchTransformedTouchEvent方法若是返回true,表示事件被消費掉了。
                // 這裏的dispatchTransformedTouchEvent方法返回值爲false,表示子View不處理事件。
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ...
                    
                    // 在addTouchTarget方法內部,會給mFirstTouchTarget賦值。
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ...
            }
            ...
        }
        ...
    }
}
複製代碼

上述代碼能夠看出,在dispatchTransformedTouchEvent方法內部,調用了child.dispatchTouchEvent方法,將事件傳遞給child。

而在咱們的例子中,MyLinearLayout的子View爲MyTextView,咱們知道TextView的onTouchEvent方法默認返回的是false,因此咱們這裏的dispatchTransformedTouchEvent方法的返回值爲false。

因此咱們這裏沒有子View處理事件,咱們的mFirstTouchTarget的值爲null。

接着看part4:

// Dispatch to touch targets.
// mFirstTouchTarget的值爲空,因此會走if內部的邏輯,會再次調用dispatchTransformedTouchEvent方法,不過此次的child參數爲null。
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    ...
}
複製代碼

上面分析過,dispatchTransformedTouchEvent方法中的child參數若是爲null的話,就會調用當前View#dispatchTouchEvent方法,進而調用View#onToucheEvent方法。

咱們沒有給MyLinearLayout設置OnTouchListener,因此MyLinearLayout的onTouchEvent方法會執行。

因此咱們能夠解釋ACTION_DOWN相關的log了:

MyRelativeLayout dispatchTouchEvent ACTION_DOWN
MyRelativeLayout onInterceptTouchEvent ACTION_DOWN
MyLinearLayout dispatchTouchEvent ACTION_DOWN
MyLinearLayout onInterceptTouchEvent ACTION_DOWN
MyTextView dispatchTouchEvent ACTION_DOWN
MyTextView onTouchEvent ACTION_DOWN
MyLinearLayout onTouchEvent ACTION_DOWN
MyRelativeLayout onTouchEvent ACTION_DOWN
複製代碼

事件傳遞給MyRelativeLayout,先調用MyRelativeLayout#dispatchTouchEvent方法,接着將MyRelativeLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted,而後調用dispatchTransformedTouchEvent方法,將事件分發給子View MyLinearLayout。

在MyLinearLayout內部也是先調用MyLinearLayout#dispatchTouchEvent方法,接着將MyLinearLayout#onInterceptTouchEvent方法的返回值(false)賦值給intercepted(part2部分),而後調用dispatchTransformedTouchEvent方法(part3部分),將事件分發給子View MyTextView。

在MyTextView內部也是先調用MyTextView#dispatchTouchEvent方法,接着會調用MyTextView#onTouchEvent方法,由於MyTextView繼承自TextView,其CLICKABLELONG_CLICKABLE屬性默認均爲false,因此MyTextView#onTouchEvent方法返回值爲false。表示事件沒有被MyTextView消費掉,因此會向上傳遞給父View。

接着事件會向上傳遞給MyLinearLayout(part4部分),調用super.dispatchTouchEvent,也就是View#dispatchTouchEvent方法,在該方法內部會調用onTouchEvent方法。不過跟MyTextView同樣,這個onTouchEvent方法也返回false。因此事件會繼續向上傳遞。

接着事件會向上傳遞給MyRelativeLayout,該過程和MyLinearLayout過程同樣。

總結一下,因爲沒有子View消耗掉ACTION_DOWN事件,當前的mFirstTouchTarget值爲null。

MotionEvent.ACTION_MOVE流程

接下來分析ACTION_MOVE事件傳遞的過程,仍是ViewGroup#dispatchTouchEvent方法:

part1:

// 若是是ACTION_DOWN事件的話,就重置狀態,包括將mFirstTouchTarget置爲null
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
複製代碼

當前爲ACTION_MOVE事件,因此不會走if內部邏輯。

接着看part2:

// Check for interception.
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); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}
複製代碼

因爲此次傳遞的是ACTION_MOVE事件,且通過ACTION_MOVE事件後,mFirstTouchTarget的值依然爲null,因此外層if的條件不知足,直接會走else分支,此時intercepted的值爲true。

因爲不走if分支,因此咱們的onInterceptTouchEvent方法也不會被再次調用。

接下來看part3:

if (!canceled && !intercepted) {
    ...
}
複製代碼

if條件語句也不知足,內部邏輯不會走,也就不會下發事件給子View了。

再往下看part4:

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
   ...
}
複製代碼

因爲咱們的mFirstTouchTarget的值爲null,因此會調用這個dispatchTransformedTouchEvent方法。因爲這裏傳遞的child參數爲null,因此實際上調用的事當前ViewGroup的onTouchEvent方法。

能夠看出ACTION_MOVE事件並無向下分發給子View,而是在調用當前View的onTouchEvent方法後,統一交給父View處理了。

咱們知道事件來源於DecorView,它繼承自FrameLayout,因此這裏的ACTION_MOVE事件默認就交給DecorView處理了,沒有向下下發,因此咱們的三個自定義View沒有收到出ACTION_DOWN之外的事件。

MotionEvent.ACTION_UP流程

這種狀況下ACTION_UP的分析與ACTION_MOVE徹底相同,這裏再也不贅述。

至此相關源碼已經分析完畢,有興趣的同窗,能夠寫一個自定義的Button,替換掉MyTextView,看下現象是否是有所不一樣,也能夠嘗試從源碼角度解釋。

參考

一、Android開發藝術探索(Android開發神書,不解釋)

相關文章
相關標籤/搜索