Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View系列教程07--詳解ViewGroup分發Touch事件

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

編輯:關於Android編程

 

在上一篇中已經分析完了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);
}

該段代碼主要邏輯如下:

處理ACTION_DOWN事件
調用onUserInteraction()該方法在源碼中為一個空方法,可依據業務需求在Activity中覆寫該方法。

利用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事件被消費;反之,則進入下一步

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 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行
在此詳細分析該段代碼:

請注意變量intercepted,請參見代碼第20行
該值用來標記ViewGroup是否攔截Touch事件的傳遞,它在後續代碼中起著重要的作用.

事件為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

將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。
在此梳理該階段的主要邏輯。

計算Touch事件的坐標,請參見代碼第56-57行
在後續的判斷中會依據坐標來判斷觸摸到了ViewGroup中的哪個子View。

依據坐標,判斷哪個子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。
在該處亦有兩種不同的情況:

處理ACTION_DOWN,請參見代碼第126-127行
如果mFirstTouchTarget!=null則說明在第四步中Touch事件已經被消費,所以不再做其他處理 處理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); 
} 

child == null
如果子View沒有消費掉Touch事件,那麼ViewGroup就將自己動手處理Touch事件,即super.dispatchTouchEvent(event)。此時,ViewGroup就化身為了普通的View,它會在自己的onTouch(),onTouchEvent()中處理Touch;這個過程之前已經分析過了,不再贅述。

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事件。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160614/20160614093433109.jpg" title="\" />

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

判斷是否需要攔截(intercepted) 處理ACTION_DOWN事件 利用mFirstTouchTarget是否為null繼續處理Touch(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

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

這裡寫圖片描述

先來瞅瞅布局文件




    

        

    


此處,我們在示例的布局文件中放入了一個自定義的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一切都是那麼清晰明了

ACTION_DOWN事件由外及裡從Activity傳遞到Button Button處理了ACTION_DOWN事件 ACTION_UP事件由外及裡從Activity傳遞到Button Button處理了ACTION_UP事件

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

return super.onTouchEvent(event);

消耗了Touch事件,所以Touch事件就沒有回傳給LinearLayoutSubclass和Activity。
現在對剛才的代碼做一點小小的修改:

在ButtonSubclass的onTouchEvent()中返回false 在LinearLayoutSubclass的onTouchEvent()中返回false

現在再次運行代碼並且輕觸Button後抬起手指,觀察一下輸出日志:
這裡寫圖片描述

ACTION_DOWN事件由外及裡從Activity傳遞到Button。 Button沒有處理ACTION_DOWN事件,將其回傳至LinearLayoutSubclass LinearLayoutSubclass也沒有處理ACTION_DOWN事件,將其回傳至Activty Activity處理ACTION_DOWN Activity處理ACTION_UP,不再分發

嗯哼,在這裡就看明白了如果子View沒有處理Touch事件就會回傳給父View,一層一層地往上回溯。在剛才這個過程中沒有一個子View處理ACTION_DOWN事件造成mFirstTouchTarget為null,所以當ACTION_UP事件發生時Activity不再將其派發給子View而是自己處理了。這個過程在之前分析源碼的時候也著重提到過。


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

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved