Android Touch事件傳遞機制全面解析(從WMS到View樹)

轉眼間近一年沒更新博客了,工做一忙起來。很是難有時間來寫博客了,由於現在也在從事Android開發相關的工做,所以之後的博文也會不少其它地專一於這一塊。html


這篇文章準備從源代碼層面爲你們帶來Touch事件的傳遞機制。我這裏分析的源代碼時Android4.4的。java

說到分析源代碼,光看確定是不行的,必定要親自去跟,並且要邊跟邊思考,因此在下一篇中。會有一個Demo來爲你們詳細分析源代碼的走向。android


如下進入正題,先來看下Android中事件的分類:數組

一、鍵盤事件:主要是指按下虛擬鍵盤的某個按鍵、或者機身的物理按鍵時產生的事件。網絡

二、鼠標事件:Android4.0以後添加了對鼠標事件的監控,如ACTION_HOVER_ENTER。app

三、觸摸屏事件:凡是觸摸屏幕而產生的事件都是觸摸屏事件。觸摸屏事件包含很是多,比方單點觸控、多點觸控)、軌跡球事件等。ide


咱們這裏主要解說單點觸控事件,也就是Touch事件的傳遞,首先看下Touch事件的完整傳遞過程:佈局

一、首先需要明確,Android中,Touch事件的分發分服務端和應用端。在Server端由WindowManagerService(WMS,窗體管理服務。不懂的自行腦補)負責採集和分發的。在client端則是由ViewRootImpl(內部有個mView變量指向View樹的根 ,負責控制View樹的UI繪製和事件消息的分發)負責分發的。post


 二、WMS在啓動以後,通過逐層調用。會在native層啓動兩個線程:InputReaderThread和InputDispatchThread,前者用來讀取輸入事件,this

後者用來分發輸入事件,輸入事件通過nativie層的層層傳遞,終於會傳遞到java層的ViewRootImpl中。調用

ViewPostImeInputStage(ViewRootImpl的內部類)中的各個方法來分發不一樣的事件,而Touch事件是在processPointerEvent方法進行分發的(這部分代碼很是單,可自行查看)。


三、processPointerEvent方法中調用mView.dispatchPointerEvent(event)方法。這裏的mView就是在建立窗體後經過調用root.setView傳進

來的DecorView,而dispatchPointerEvent方法會對event進行推斷,假設是Touch事件的話。就調用dispatchTouchEvent將該事件分發DecorView,這樣。Touch事件就傳遞到了View樹中了。



Touch事件從WMS到ViewRootImpl的傳遞

如下這張圖(不是本身畫的。網上找的,Android4.4中,InputManager變成了InputManagerService,ViewRoot變成了ViewRootImpl)展現了Touch事件

從WMS(sever端)傳遞到ViewRootImpl(client端)的流程。


      這裏需要特別注意的是。Touch事件從server端傳遞到client端採用的IPC方式並不是Binder。而是共享內存和管道,至於爲何不採用Binder,應該是共享內存的效率更高,而管道(注意。兩個管道。分別負責不一樣方向的讀和寫)僅僅負責通知是否有事件發生,傳遞的僅僅是一個很是easy的字符串。所以並不會太多地影響到IPC的效率。


      上圖中,僅僅有WMS、ViewRootImpl、InputManagerService、InputQueue是在FrameWork層實現的。其它部分都是在native層實現的,native 層的代碼沒有細看,參考了些網上的一些資料和公司內部的一些分享,把整個流程串通了。這部分代碼,假設時間充足,可以深刻研究下。


      在sever端中。InputReader和InputDispatcher是native 層的兩個線程,前者不斷地從EventHub中讀取事件(包含所有的事件。對不一樣的事件會作推斷處理)。後者則不斷地分發InputReader讀取到的事件。而實際的分發操做時在InputPublish中進行的,它裏面保存的有一個指向server端InputChannel端的指針。和一個指向ShareMemory(共享內存)的指針。當有事件要分發時,它會將事件寫入到ShareMemory中,並且傳遞一個特定的字符串給InputChannel。inutChannel將該字符串寫入到管道中。在client端,一旦InputChannel從管道中讀取到有事件分發過來,便會通知InPutConsumer從ShareMemory中讀取詳細的事件。並傳遞到framework層的InputQueue中。相同。一旦事件消費完畢,client端會經過管道告訴server端,事件已經消費完畢,流程與上面的似。


大體的流程就是這樣。


另外。順便說下這裏的兩個InputChannel,這兩個InputChannel是在native 層的。他們在framework層各自有一個相應的InputChannel類,對於這兩個framework層的InputChannel。client端的是在ViewRootImpl中的setView中new出來的,但是並未作不論什麼初始化操做(真正的初始化操做是跟server端的一塊兒在WMS中運行的)。也就是構造方法裏面爲空。

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }

server端的InputChannel儘管是在server端建立的,但其建立過程是在client端發起的。ViewRootImpl中有server端的Session的代理,相同是setview方法中。經過Session代理運行server端Session的addToDisplay方法。該方法接受了client端的InputChannel方法

mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);

addToDisplay方法在Session類中,它會調用WindowManagerService的addWindow方法。而兩個InputChannel的初始化操做都是在這裏面的這這段代碼中進行的。

            if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                win.setInputChannel(inputChannels[0]);
                inputChannels[1].transferTo(outInputChannel);

                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }

這裏的InputChannel.openInputChannelPair方法會在native層建立兩個InputChannel,也就是咱們上面看到的那兩個,並返回相應的framework層InputChannel的兩個對象,保存在iputChannels數組中,當中一個保留字server端,一個經過transferTo方法返回到client 端。

這裏的InputChannel.openInputChannelPair方法和transferTo方法中都是直接調用來了native方法,這裏再也不貼代碼。



       說了這麼多。事實上就是一個Binder機制,關於Binder機制。你們自行在搜索吧,入門的資料仍是挺多的。

這裏我依據源代碼畫了兩份ViewRootImpl和WMS之間經過Binder 機制進行IPC的序列圖,有興趣的可以自行研究下代碼,僅僅要能搞清楚Binder機制。這部分代碼還就不難懂。


ViewRootImpl到WMS的鏈接,經過WMS提供給ViewRootImpl的IWinowSession成員,也就是Session在本地的代理來完畢:

 
  


   

     WMS到 ViewRootImpl的鏈接,經過ViewRootImpl提供給WMSIwindow(ViewRootImpl的內部類)來完畢



Touch事件在View樹中的分發

當Touch事件傳遞到了ViewRootImpl中後,就會在View樹中進行分發,要了解Touch事件在View樹中的分發。首先需要了解View樹的建立,這部分又可以寫成一篇單獨的博文了。詳細的過程這裏再也不詳說,有興趣的可以本身研究下。

View樹建立完畢後的結構是這種(圖片源自網絡):


View樹的根View永遠是DecorView,它繼承自FrameLyout。其內部會有一個LinearLayout,依據Window Feather的的不一樣,LinearLayout內部的佈局也不一樣,當中每種不一樣佈局的xml(系統資源內部的xml佈局)內都有一個id爲content的FrameLayout。這就是咱們在本身的佈局所attach的父容器。


Touch事件的傳遞天然是先從ViewRootImpl傳遞到DecorView中。這個前面的第三點也說到了,所以咱們這裏就從DecorView入手,開始分析Touch事件的分發。


在開始分析以前,先大體梳理下Touch事件傳遞可能涉及到的一些基礎:

一、普通狀況下。每一個Touch事件,老是以ACTION_DOWN事件開始。中間穿插着一些ACTION_MOVE事件(取決因而否有手勢的移動),而後以ACTION_UP事件結束。中間還會會有onTouch、Click、LongClick等事件。


二、事件分發過程當中,包含對MotionEvent事件的三種處理操做:
分發操做:dispatchTouchEvent方法,後面兩個方法都是在該方法中被調用的。
攔截操做:onInterceptTouchEvent方法(ViewGroup)
消費操做:onTouchEvent方法和OnTouchListener的onTouch方法,當中onTouch的優先級高於onTouchEvent,若onTouch返回true。那麼就不會調用onTouchEvent方法。


三、dispatchTouchEvent分發Touch事件是自頂向下,而onTouchEvent消費事件時自底向上。onTouchEvent和onIntercepteTouchEvent都是在dispatchTouchEvent
中被調用的。


如下,正式進入對Touch事件在View樹中分發的分析:

首先來看DecorView(PhoneWindow的內部類)中dispatchTouchEvent方法:

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }


這裏的cb就是當前的Activity,Activity實現了Window.Callback接口,同一時候在Activity的attach方法中。建立PhoneWindow後。調用了
mWindow.setCallback(this)將PhoneWindow中的callback設置爲當前的的Activity,所以這裏cb.dispatchTouchEvent就是Activity的
dispatchTouchEvent方法,假設前面三個條件同一時候成立(一般是都成立的),則調用Activity的dispatchTouchEvent方法進行事件的分發。
不然。直接調用super.dispatchTouchEvent方法,也便是FrameLayout的dispatchTouchEvent方法,事實上即便調用了Activity的
dispatchTouchEvent方法。終於也是會調用到super.dispatchTouchEvent。咱們可以繼續往下看Activity的dispatchTouchEvent方法:

 
  

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

前面if分支不用管,這裏會調用PhoneWindow的superDispatchTouchEvent方法。進去看看:

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

調用了DecorView的superDispatchTouchEvent方法,再進去看看:

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

終於仍是調用來DecorView的super.dispatchTouchEvent。也就是說。無論如何。DecorView的dispatchTouchEvent終於都會調用到本身父親FrameLayout的dispatchTouchEvent方法。而咱們在FrameLayout中找不到dispatchTouchEvent方法。因此,會去運行ViewGroup的
dispatchTouchEvent方法。

假設該dispatchTouchEvent返回true,說明後面有view消費掉了該事件,那就返回true,不會再去運行自身的onTouchEvent方法,不然,說明沒有view消費掉該事件,會一路回傳到Activity中,而後調用本身的onTouchEvent方法。該方法的實現比較簡單。例如如下:

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
    }

首先調用mWindow.shouldCloseOnTouch方法來推斷是否需要關閉窗體。假設是,則finish掉該Activity,並返回true,不然。返回false,普通狀況下,是返回false的,那何時回返回true呢?咱們來看下Window類中的shouldCloseOnTouch方法:

    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }


這裏的大體意思是,假設設置了mCloseOnTouchOutside屬性爲true(相應xml中的android:windowCloseOnTouchOutside屬性),且當前事件爲down事件。且down事件發生在該Activity範圍以外,並且DecorView不爲null,就返回true。很是明顯。dialog形的Activity可能會發生這種狀況。


如下需要重點來看下ViewGroup中的dispatchTouchEvent方法了:

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

	//handled爲返回值,表示是否有view消費了該事件。
        boolean handled = false;
	//是否要過濾掉該Touch事件。大體是這個意思
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

           if (actionMasked == MotionEvent.ACTION_DOWN) {
//由於down事件表明一個系列事件的開始,所以假設是down事件,
//一、就清空掉曾經消費事件的目標view,這裏主要指清空掉mFirstTouchTarget鏈表(保存接受Touch事件的單鏈表,這點在後面的代碼中會看到)。並將mFirstTouchTarget置爲null。
//二、重置觸摸狀態,重置了disallowIntercept相應的標誌位。該變量的值決定了onInterceptTouchEvent方法是否有效,這點後面咱們會看到;還有就是重置來View的mPrivateFlags標誌位,這個沒去了解詳細是幹嗎用的。

通常在發生app的切換。或者ANR等狀況時。代碼會走到這裏,這一點源代碼的凝視裏也有。

cancelAndClearTouchTargets(ev); resetTouchState(); } // 標記是否要攔截該Touch事件,true表示攔截。false表示不攔截 final boolean intercepted; //假設當前事件爲down事件。或者可接受Touch事件的鏈表不爲空。就運行if語句裏的邏輯。這裏注意, //一、由於down事件是一個完整事件序列的的起點,所以當發生down事件時。邏輯走到這裏。尚未找到消費down事件的view,所以mFirstTouchTarget爲null, //二、而對後面的move和up事件,假設前面的down事件被某個view消費掉了,則mFirstTouchTarget不爲null。

上面兩種狀況都會使代碼進入if分支中來。 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //是否不一樣意攔截。默以爲false,也就是贊成該方法可以經過 requestDisallowInterceptTouchEvent方法來設置 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 假設贊成攔截,則onInterceptTouchEvent有效,依據咱們覆寫的該方法的返回值來推斷是否攔截,不然。onInterceptTouchEvent無效,不正確該事件進行攔截。 if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // 假設當前事件不是down事件。且以前在分發down事件的時候沒有找到消費down事件的目標view,也即mFirstTouchTarget爲null,則直接攔截該事件。 intercepted = true; } // 檢查當前事件是否被取消 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; //保存消費事件的目標View所相應的 TouchTarget對象 TouchTarget newTouchTarget = null; //事件是否已經分發到了目標View中。

boolean alreadyDispatchedToNewTouchTarget = false; // 假設沒有被取消。並且沒有被攔截,就分發該事件,注意僅僅有down事件纔會走到這裏去分發。對於move和up事件。則會跳過這裏。直接從 mFirstTouchTarget鏈表中找到以前消耗down事件的目標View,直接將move和up事件非法給它,後面的代碼中咱們會分析到。

if (!canceled && !intercepted) { //僅僅有down事件會走到這裏 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //Touch事件的index,對於單點觸控。一直爲0,這裏不用深究 final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); //該ViewGroup中子View的個數 final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { //當前事件發生的位置 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //保存該ViewGroup中子View final View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); //遍歷子View,找到能消費該down事件的子View,對於類型爲ViewGroup的子View,在分發的時候。會遞歸調用到它的dispatchTouchEvent方法繼續進行分發。 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; //假設當前子View可以消費該down事件。並且該down事件發生的位置在當前子View的範圍內,則繼續運行。將down事件分發給它,不然,continue推斷下一個子View能否接受該down事件。 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } //推斷該能接受該down事件的child是否已經在mFirstTouchTarget鏈表中。假設在的話,說明child已經消費掉了該down事件,直接跳出循環。我在寫demo跟代碼時,沒有一次走到這裏的,臨時不是很是清楚,如何的場景下,代碼會運行到這裏的break。 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //假設該child尚未消費掉該down事件,就直接調用dispatchTransformedTouchEvent方法將該down事件傳遞給該child,該方法裏面會調用到child的dispatchTouchEvent方法,假設該方法返回true,則說明child消費掉了該down事件,那麼就運行if語句裏的邏輯,將child添加到mFirstTouchTarget鏈表的表頭,並且將該表頭賦值給newTouchTarget(參見addTouchTarget方法),同一時候 alreadyDispatchedToNewTouchTarget置爲true。說明有子view消費掉了該down事件。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } //假設newTouchTarget爲null。並且 mFirstTouchTarget不爲null,也即沒找到子View來消耗該事件,但是保存Touch事件的鏈表不爲空,則把newTouchTarget賦值爲最先加進(Least Recently added)mFirstTouchTarget鏈表的target。臨時沒全然搞明確這裏的詳細意思,跟代碼都沒有走到這裏。 if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } //後面在處理MOVE和UP事件時。會直接依據上次的DOWN是否被消費掉來直接進行相應的處理。

if (mFirstTouchTarget == null) { // 假設沒有子view接受該事件,則直接把當前的ViewGroup看成普通的View看待,把事件傳遞給本身(詳見dispatchTransformedTouchEvent方法,注意第三個參數傳遞的是null)。

handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 假設以前的DOWN事件被子view消費掉了。就會直接找到該子View相應的Target,將MOVE或UP事件傳遞給它們。 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { //假設該事件已經被消費掉了,則再也不進行分發(該分支主要針對DOWN事件) handled = true; } else { //不然。就直接將DOWN或UP事件分發給目標Target(以前消費DOWN事件的view相應的target,注意dispatchTransformedTouchEvent的第三個參數爲target.child),這裏要注意的是,假設intercepted爲true,也就是MOVE或UP事件被攔截了,則cancelChild爲true,則會分發一次CANCLE事件(注意dispatchTransformedTouchEvent的第二個參數)。 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; } } // 假設當前事件是CANCLE或UP。會調用resetTouchState方法。清空Touch狀態,這裏會清空mFirstTouchTarget鏈表,並將mFirstTouchTarget置爲null 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; }


另外,畫了張ViewGroup中dispatchTouchEvent方法代碼運行的流程圖。可以有助於你們對代碼整體邏輯的把握(Windows上viso中畫的圖,傳到mac上就變成這樣了,又一次保存成圖片,清楚度過低,直接在PPT裏面截出來了。湊合着看吧。沒太大影響)。



關於上面提到的dispatchTransformedTouchEvent方法,這裏就很少分析了,感興趣可以本身分析下,另外,ViewGroup中沒有複寫onTouchEvent方法。


如下重點看下View中的dispatchTouchEvent方法。

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

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

很是明顯。會先推斷該View有沒有綁定OnTouchListener監聽器,假設綁定了,並且複寫了當中的onTouch方法。假設onTouch方法返回了true,那麼Touch事件就被消費掉了,後面的onTouchEvent方法就不會獲得運行,而假設沒有被消費掉。纔會運行到onTouchEvent方法。依據其返回值來斷定Touch時間是否被消費掉。

這裏重點關注消費Touch事件的前後順序:onTouch先於onTouchEvent。


如下就關鍵來看下View中的onTouchEvent方法了。

    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true);
                       }

                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }

這裏事實上沒太多要說的,重點關注:


一、onClick和onLongClick運行的時機:onClick時在UP事件中運行的,onLongClick實在Down事件中運行的,僅僅是假設在Down事件中已經運行了onLongClick的話,則mHasPerformedLongPress變量會被置爲true,這樣在UP事件中。就會把onClick的回調remove掉。就不會再運行onClick了。


二、僅僅要該View是clickable的,就必定會消費掉Touch事件,僅僅是,假設該View是Disable的話,儘管消費掉了Touch事件。但是不作不論什麼處理。


另外。有一點大體說下:
源代碼的前面部分有一個mTouchDelegate變量(默以爲null),假設它不爲null的話。會將Touch事件分發給它。

詳細的意思是這種。假設有兩個視圖v1和touchDelegate1。它們的佈局相互之間不重疊。假設設置了v1.setTouchDelegate(touchDelegate1)的話,v1的觸摸事件就會分發給touchDelegate1中的view(TouchDelegate中有一個view變量)

 


爲了便於整體上對源代碼流程的把握,這裏相同畫了一個流程圖



最後,關於整個Touch事件在View樹中的傳遞流程,相同畫了張流程圖,看起來會更直觀。有助於對整體流程的把控:



以上流程圖中有些地方文字有錯位。應該不影響對流程的整體理解和把握。


事實上相對來講,事件的分發處理屬於Android中比較基礎的知識點。但想把整個流程完整地串通,仍是要花些時間的。這篇文章在10月份的時候就想寫了,但是工做後,寫博客的時間愈來愈少。人也變得愈來愈懶了。

。。

整篇文章斷斷續續堅持着寫下來仍是挺費勁的。


好了,很少說了,下篇文章,將經過一個Demo,結合8種不一樣的狀況,對Touch事件在View樹中傳遞時源代碼的運行狀況作一個詳細的分析。


          知乎上可以看到一個不同的我,歡迎關注:蘭亭風雨的知乎首頁

         一直想作個公衆號。但是考慮到在手機端看代碼的用戶體驗確實太差。而我又比較喜歡正能量的東東。最後倒騰了一個雞湯號,每日一篇正能量好文,同一時候各類互聯網信息爆料。盡在當中。

。。

歡迎掃碼關注。

相關文章
相關標籤/搜索