自定義View系列教程07--詳解ViewGroup分發Touch事件


深刻探討Android異步精髓Handlerjava


站在源碼的肩膀上全解Scroller工做機制android


Android多分辨率適配框架(1)— 核心基礎
Android多分辨率適配框架(2)— 原理剖析
Android多分辨率適配框架(3)— 使用指南markdown


自定義View系列教程00–推翻本身和過往,重學自定義View
自定義View系列教程01–經常使用工具介紹
自定義View系列教程02–onMeasure源碼詳盡分析
自定義View系列教程03–onLayout源碼詳盡分析
自定義View系列教程04–Draw源碼分析及其實踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發Touch事件
自定義View系列教程08–滑動衝突的產生及其處理app


PS:若是以爲文章太長,那就直接看視頻框架


在上一篇中已經分析完了View對於Touch事件的處理,在此基礎上分析和理解ViewGroup對於Touch事件的分發就會相對容易些。
當一個Touch事件發生後,事件首先由系統傳遞給當前Activity而且由其dispatchTouchEvent()派發該Touch事件,源碼以下:異步

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

該段代碼主要邏輯以下:ide

  1. 處理ACTION_DOWN事件
    調用onUserInteraction()該方法在源碼中爲一個空方法,可依據業務需求在Activity中覆寫該方法。
  2. 利用PhoneWindow的superDispatchTouchEvent()派發事件工具

    @Override 
    public boolean superDispatchTouchEvent(MotionEvent event) { 
           return mDecor.superDispatchTouchEvent(event); 
    }

    DecorView的superDispatchTouchEvent()源碼以下:源碼分析

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    此處咱們能夠看到:在該方法中又將Touch事件交給了DecorView進行派發。
    DecorView繼承自FrameLayout它是整個界面的最外層的ViewGroup。
    至此,Touch事件就已經到了頂層的View且由其開始逐級派發。若是superDispatchTouchEvent()方法最終true則表示Touch事件被消費;反之,則進入下一步佈局

  3. Activity處理Touch事件
    若是沒有子View消費Touch事件,那麼Activity會調用自身的onTouchEvent()處理Touch.

在以上步驟中第二步是咱們關注的重點;它是ViewGroup對於Touch事件分發的核心。
關於dispatchTouchEvent(),請看以下源碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        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;

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                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); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }


            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;

            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                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;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        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);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)||!isTransformedTouchPointInView(x,y,child,null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            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) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
            } else {
                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;
                }
            }

            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;
    }

第一步:
清理和還原狀態,請參見代碼第15-18行
ACTION_DOWN是一系列Touch事件的開端,當Touch爲ACTION_DOWN時須要進行一些初始化和還原操做。好比:清除以往的Touch狀態(state)和開始新的手勢(gesture)。因此在cancelAndClearTouchTargets( )中將mFirstTouchTarget設置爲null,且在resetTouchState()中重置Touch狀態標識

第二步:
檢查是否須要ViewGroup攔截Touch事件,請參見代碼第20-31行
在此詳細分析該段代碼:

  1. 請注意變量intercepted,請參見代碼第20行
    該值用來標記ViewGroup是否攔截Touch事件的傳遞,它在後續代碼中起着重要的做用.
  2. 事件爲ACTION_DOWN或者mFirstTouchTarget不爲null時檢查是否須要ViewGroup攔截Touch事件,請參見代碼第21行

    if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget !=null)

    ACTION_DOWN表示Touch事件是手指按下的事件,那麼mFirstTouchTarget又是什麼意思呢?mFirstTouchTarget是TouchTarget類的對象,而TouchTarget是ViewGroup中的一個內部類,它封裝了被觸摸的View及此次觸摸所對應的ID,該類主要用於多點觸控。好比:三個指頭依次按到了同一個Button上。
    咱們沒必要過多的理會TouchTarget,可是要重點關注mFirstTouchTarget。
    mFirstTouchTarget貫穿dispatchTouchEvent(),對於流程的走向發揮着相當重要的做用。
    (1) mFirstTouchTarget不爲null
    表示ViewGroup沒有攔截Touch事件而且子View消費了Touch
    (2) mFirstTouchTarget爲null
    表示ViewGroup攔截了Touch事件或者雖然ViewGroup沒有攔截Touch事件可是子View也沒有消費Touch。總之,此時須要ViewGroup自身處理Touch事件

    若是ACTION_DOWN事件被子View消費(即mFirstTouchTarget!=null),當處理後續到來的ACTION_MOVE和ACTION_UP時仍會調用該代碼判斷是否須要攔截Touch事件。

    2.1 判斷disallowIntercept(禁止攔截)標誌位,請參見代碼第22行
    ViewGroup能夠攔截Touch事件,可是它的子View可調用 getParent().requestDisallowInterceptTouchEvent(true)禁止其父View的攔截。其實,從這個較長的方法名也能夠看出來它的用途——禁止事件攔截;在該方法內部會改變FLAG_DISALLOW_INTERCEPT的值。

    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    因此利用此行代碼判斷是否禁止攔截(disallowIntercept)。

    在此請注意:
    ViewGroup中的requestDisallowInterceptTouchEvent( )方法能夠用來禁止或容許ViewGroup攔截Touch事件,可是它對於ACTION_DOWN是無效的。
    也就是說子View能夠禁止父View攔截ACTION_MOVE和ACTION_UP可是沒法禁止父View攔截ACTION_DOWN。由於在ACTION_DOWN時會調用resetTouchState()重置了FLAG_DISALLOW_INTERCEPT的值致使子View對該值設置失效。因此,對於ACTION_DOWN事件ViewGroup總會調用onInterceptTouchEvent()判讀是否要攔截Touch事件

    2.2 處理disallowIntercept的值爲false的狀況,請參見代碼第23-25行
    若disallowIntercept(禁止攔截)的值爲false,因此調用onInterceptTouchEvent()攔截Touch並將結果賦值給intercepted。
    常說ViewGroup的事件傳遞中的流程是:

    dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent

    其實在這就是一個體現:dispatchTouchEvent()中調用了onInterceptTouchEvent()。

    2.3 處理disallowIntercept的值爲true的狀況,請參見代碼第27行
    若disallowIntercept(禁止攔截)的值爲true,表示不攔截Touch事件。
    因此將intercepted設置爲false

  3. 將intercepted設置爲true,請參見代碼第30行
    若是不是ACTION_DOWN事件而且mFirstTouchTarget爲null,那麼直接將intercepted設置爲true,表示ViewGroup攔截Touch事件。
    更加直白地說:若是ACTION_DOWN沒有被子View消費(mFirstTouchTarget爲null)那麼當ACTION_MOVE和ACTION_UP到來時ViewGroup再也不去調用onInterceptTouchEvent()判斷是否須要攔截而是直接的將intercepted設置爲true表示由其自身處理Touch事件

第三步:
檢查cancel,請參見代碼第38行

第四步:
分發ACTION_DOWN事件,請參見代碼第43-117行

if (!canceled && !intercepted)

若是Touch事件沒有被取消也沒有被攔截,那麼ViewGroup將類型爲ACTION_DOWN的Touch事件分發給子View。
在此梳理該階段的主要邏輯。

  1. 計算Touch事件的座標,請參見代碼第56-57行
    在後續的判斷中會依據座標來判斷觸摸到了ViewGroup中的哪一個子View。
  2. 依據座標,判斷哪一個子View接收Touch事件,請參見代碼第61-105行
    這部分代碼的主要操做爲:
    在找到能夠接收Touch事件的子View後調用dispatchTransformedTouchEvent()方法將Touch事件派發給該子View。
    第一種狀況:
    子View沒有消費Touch事件則該方法的返回值爲false,此時mFirstTouchTarget仍爲null
    第二種狀況:
    子View消費掉了Touch事件那麼該方法的返回值爲true,而後執行

    newTouchTarget = addTouchTarget(child, idBitsToAssign);

    在addTouchTarget()方法內將該子View添加到mFirstTouchTarget鏈表的表頭,而且爲mFirstTouchTarget設值使其不爲null。隨後將alreadyDispatchedToNewTouchTarget置爲true,表示已經將Touch事件分發到了子View,或者說子View消費掉了Touch事件

小總結:
在這個步驟中只有找到了能夠消費Touch事件的子View時mFirstTouchTarget纔不爲null;其他狀況好比未找到能夠接收Touch事件的子View或者子View不能消費Touch事件時mFirstTouchTarget仍爲null

小疑惑:
請參見代碼第78-82行:
爲何newTouchTarget!=null就會執行break跳出for循環了呢?
還記得這個for循環的做用是什麼嗎?——尋找一個能夠接受Touch事件的子View。
若是先有個指頭按在了子View上(即ACTION_DOWN),而後另外一根指頭又按在相同的子View上(即ACTION_POINTER_DOWN)。這種多點觸摸的狀況下兩個指頭按在了同一個View上,當第一指頭按下的時候一個TouchTarget就已經記錄了該子View,因此當第二個指頭再按下的時候固然仍是由這個子View來處理Touch事件,也就是說沒有再繼續尋找的必要了。

第五步:
繼續事件分發,請參見代碼第119-147行
在第四步對於ACTION_DOWN事件作了一些特有處理,在此繼續進行事件的分發。不管是ACTION_DOWN仍是ACTION_MOVE和ACTION_UP均會進入該步驟。
第一種狀況:
mFirstTouchTarget==null
它表示Touch事件被ViewGroup攔截了根本就沒有派發給子view或者雖然派發了可是在第四步中沒有找到可以消費Touch事件的子View。
此時,直接調用dispatchTransformedTouchEvent()方法處理事件
第二種狀況:
mFirstTouchTarget != null,表示找到了可以消費Touch事件的子View。
在該處亦有兩種不一樣的狀況:

  1. 處理ACTION_DOWN,請參見代碼第126-127行
    若是mFirstTouchTarget!=null則說明在第四步中Touch事件已經被消費,因此再也不作其餘處理
  2. 處理ACTION_MOVE和ACTION_UP,請參見代碼第129-143行
    調用dispatchTransformedTouchEvent()將事件分發給子View處理,請參見代碼第130行

結合第四步和第五步,在此思考一個問題:
ViewGroup將ACTION_DOWN分發給子View,若是子View沒有消費該事件,那麼當ACTION_MOVE和ACTION_UP到來的時候系統還會將Touch事件派發給該子View麼?
答案是否認的——若是子View沒有處理ACTION_DOWN那麼它就失去了處理ACTION_MOVE和ACTION_UP的資格。
在第四步中若是子View處理了ACTION事件那麼mFirstTouchTarget不爲null,當ACTION_MOVE和ACTION_UP到來時會跳過第四步進入到第五步。在第五步中就會判斷mFirstTouchTarget是否爲null,若是爲空那麼ViewGroup自身會處理Touch;若是不爲空那麼繼續由mFirstTouchTarget處理Touch事件。

第六步:
清理數據和狀態還原,請參見代碼第149-155行
在手指擡起或者取消Touch分發時清除原有的相關數據


在分析dispatchTouchEvent()源碼時屢次調用dispatchTransformedTouchEvent(),在次對其源碼作一個簡略的分析

private boolean dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) {
        final boolean handled;

        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        if (newPointerIdBits == 0) {
            return false;
        }


        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }


        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        transformedEvent.recycle();
        return handled;
    }

這段代碼不算很複雜,先來瞅瞅官方文檔的介紹

Transforms a motion event into the coordinate space of a particular child view,filters out irrelevant pointer ids, and overrides its action if necessary.If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.

該方法的主要做用是將Touch事件傳遞給特定的子View(即該方法的第三個輸入參數child),由子Viwe繼續分發處理Touch事件。
但咱們發現:在系統調用dispatchTransformedTouchEvent()時該方法的第三個參數有時候是一個子View(好比dispatchTouchEvent()源碼中第85和130行),有時候又是null(好比dispatchTouchEvent()源碼中第120行).
那麼該方法第三個參數child是否爲null對於Touch事件的分發有什麼影響呢?
在dispatchTransformedTouchEvent()源碼中可見屢次對於child是否爲null的判斷且均作出以下相似的操做:

if (child == null) { 
    handled = super.dispatchTouchEvent(event); 
 } else { 
     handled = child.dispatchTouchEvent(event); 
}
  1. child == null
    若是子View沒有消費掉Touch事件,那麼ViewGroup就將本身動手處理Touch事件,即super.dispatchTouchEvent(event)。此時,ViewGroup就化身爲了普通的View,它會在本身的onTouch(),onTouchEvent()中處理Touch;這個過程以前已經分析過了,再也不贅述。

  2. child != null
    此時會調用該子View(固然該view多是一個View也多是一個ViewGroup)的dispatchTouchEvent()繼續處理Touch,即child.dispatchTouchEvent(event)。

    小結:
    若是ViewGroup攔截了Touch事件或者子View不能消耗掉Touch事件,那麼ViewGroup會在其自身的onTouch(),onTouchEvent()中處理Touch
    若是子View消耗了Touch事件父View就不能再處理Touch.

至此咱們就明白了:
Touch事件的傳遞順序爲
Activity–>外層ViewGroup–>內層ViewGroup–>View
Touch事件的消費順序爲
View–>內層ViewGroup–>外層ViewGroup–>Activity
其實,在咱們日常的工做中也能夠見到相似的場景。
開發任務的派發順序爲
CEO–>CTO–>manager–>developer
開發任務的反饋順序爲
developer–>manager–>CTO–>CEO
公司要作一個APP,CEO會將該任務交給CTO;CTO又找到了項目經理,最後項目經理將該任務分配給了開發人員。
在開發人員分析完項目後發現本身能力沒法勝任因而就將該問題拋給了項目經理,項目經理以爲本身時間有限也完成不了又拋給了CTO,CTO一樣由於某些因素沒法按時完成該任務因而又將該任務拋給了CEO。
通過這麼一圈折騰公司就以爲這個開發人員技術不是特別好,因而與該項目有關的後續工做也就不會再讓這個開發人員參與了。這就像剛纔提到的同樣:若是子View沒有消費ACTION_DOWN那麼ACTION_MOVE和ACTION_UP也就不會再派發給它。
這個過程與ViewGroup對於Touch事件的分發是很是相似的。


至此,ViewGroup對於Touch事件的分發處理的主要流程就分析完了。
爲了梳理整個dispatchTouchEvent()的脈絡,我又畫了兩個流程圖。

這裏寫圖片描述

該流程圖描述Touch事件的傳遞和消費順序。

在Touch事件的傳遞過程當中,若是上一級攔截了Touch那麼其下一級就沒法在收到Touch事件。
在Touch事件的消費過程當中,若是下一級消費Touch事件那麼其上一級就沒法處理Touch事件。

這裏寫圖片描述

該流程描述了dispatchTouchEvent( )中對於Touch分發。
這部分源碼稍微有些複雜。結合此圖,能夠將整個流程分爲三個階段:

  1. 判斷是否須要攔截(intercepted)
  2. 處理ACTION_DOWN事件
  3. 利用mFirstTouchTarget是否爲null繼續處理Touch(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

嗯哼,源碼分析完了,流程圖也有了,咱們再經過示例來驗證和理解ViewGroup對於Touch事件的分發。

這裏寫圖片描述

先來瞅瞅佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.stay4it.testtouch1.MainActivity">

    <com.stay4it.testtouch1.LinearLayoutSubclass  android:layout_width="match_parent" android:layout_height="match_parent">

        <com.stay4it.testtouch1.ButtonSubclass  android:id="@+id/button" android:layout_centerInParent="true" android:layout_width="500px" android:layout_height="500px" android:background="#FF3A9120" android:textSize="50px" android:text="Touch Me" />

    </com.stay4it.testtouch1.LinearLayoutSubclass>

</RelativeLayout>

此處,咱們在示例的佈局文件中放入了一個自定義的LinearLayout和Button。
先來看看這個自定義的線性佈局LinearLayoutSubclass

package com.stay4it.testtouch1;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/** * 原創做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class LinearLayoutSubclass extends LinearLayout {
    public LinearLayoutSubclass(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中調用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中調用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中調用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中調用onInterceptTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中調用onInterceptTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中調用onInterceptTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中調用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中調用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中調用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
}

在這個自定義的線性佈局中主要是輸出一些便於驗證的日誌信息,好比在dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()中對於ACTION_DOWN和ACTION_MOVE以及ACTION_UP均輸出對於信息。

再來看看自定義的按鈕ButtonSubclass

package com.stay4it.testtouch1;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

/** * 原創做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class ButtonSubclass extends Button{
    public ButtonSubclass(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> ButtonSubclass中調用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> ButtonSubclass中調用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> ButtonSubclass中調用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> ButtonSubclass中調用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> ButtonSubclass中調用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> ButtonSubclass中調用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}

在這個自定義Button中的處理和LinearLayoutSubclass很是相似只不過View是沒有onInterceptTouchEvent()罷了,故此,再也不贅述。

最後請看Activity的代碼實現

package com.stay4it.testtouch1;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
/** * 原創做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class MainActivity extends AppCompatActivity {
   private ButtonSubclass mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        mButton= (ButtonSubclass) findViewById(R.id.button);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> MainActivity中調用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> MainActivity中調用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> MainActivity中調用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> MainActivity中調用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> MainActivity中調用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> MainActivity中調用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
}

在該Activity中除了日誌輸出亦無其他操做。

當手指輕觸Button再擡起後,咱們瞅瞅輸出日誌。
這裏寫圖片描述
看到這些Log一切都是那麼清晰明瞭

  1. ACTION_DOWN事件由外及裏從Activity傳遞到Button
  2. Button處理了ACTION_DOWN事件
  3. ACTION_UP事件由外及裏從Activity傳遞到Button
  4. Button處理了ACTION_UP事件

在這個過程當中把Touch事件從Activity傳遞到最裏層某個子View的過程體現得很清楚和完整。可是對於Touch事件的消費過程怎麼沒有體現出來呢?不是說Touch事件的消費順序和傳遞過程是反過來的麼?在這裏怎麼沒有體現呢?
嗯哼,這是由於Button在onTouchEvent()中執行

return super.onTouchEvent(event);

消耗了Touch事件,因此Touch事件就沒有回傳給LinearLayoutSubclass和Activity。
如今對剛纔的代碼作一點小小的修改:

  1. 在ButtonSubclass的onTouchEvent()中返回false
  2. 在LinearLayoutSubclass的onTouchEvent()中返回false

如今再次運行代碼而且輕觸Button後擡起手指,觀察一下輸出日誌:
這裏寫圖片描述

  1. ACTION_DOWN事件由外及裏從Activity傳遞到Button。
  2. Button沒有處理ACTION_DOWN事件,將其回傳至LinearLayoutSubclass
  3. LinearLayoutSubclass也沒有處理ACTION_DOWN事件,將其回傳至Activty
  4. Activity處理ACTION_DOWN
  5. Activity處理ACTION_UP,再也不分發

嗯哼,在這裏就看明白了若是子View沒有處理Touch事件就會回傳給父View,一層一層地往上回溯。在剛纔這個過程當中沒有一個子View處理ACTION_DOWN事件形成mFirstTouchTarget爲null,因此當ACTION_UP事件發生時Activity再也不將其派發給子View而是本身處理了。這個過程在以前分析源碼的時候也着重提到過。


至此,關於ViewGroup對於Touch事件的分發就所有分析完了。

PS:若是以爲文章太長,那就直接看視頻


who is the next one? ——> demo

相關文章
相關標籤/搜索