1. 程式人生 > >關於Android事件分發機制的巨集觀理解(簡單)

關於Android事件分發機制的巨集觀理解(簡單)

我儘量不打錯別字,用詞準確,不造成閱讀障礙。

之所以說是巨集觀,是因為我不會寫demo,一步步截圖給你看,我會拿原始碼然後剔除暫時用不到的部分,讓你從大脈絡上理解事件分發機制,這樣你會發現,很簡單。

首先在腦海中應該有一個例子,一個LinearLayout裡面有一個button,沒了,然後你設定了button的setOnClickListener方法,很簡單的例子,也是最能接受的例子。

事件序列: 這是一個集合概念,一個完整的事件序列包含點選、移動、擡起等事件,一般就是down、move、up這三個。

這時你點選了button,就是最普通的點選,這時傳遞了一個down事件和一個up事件,普通點選一般不會觸發move事件。

先說down事件,down通過螢幕傳遞給你的Activity(怎麼傳的不糾結),Activity會傳遞給你的LinearLayout,並觸發了dispatchTouchEvent(MotionEvent ev),dispatch是分發的意思,這中間是怎麼傳的先忽略,總之就是down傳到了你的LinearLayout,要求觸發dispatchTouchEvent(MotionEvent ev)方法(down就儲存在其中的MotionEvent中),看一下LinearLayout,發現沒有這個方法,但是它繼承了ViewGroup,ViewGroup是有這個方法的,所以根據繼承規則會呼叫ViewGroup的dispatchTouchEvent方法去分發事件,disaptchTouchEvent方法很長,我們擷取重要部分:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    //...省略部分程式碼
    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; } final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; //***************************省略部分程式碼&&分界線***********************************// if (!canceled && !intercepted) {//如果攔截了,這方法根本進不來 //...省略部分程式碼 if (newTouchTarget == null && childrenCount != 0) { //...省略部分程式碼 final View[] children = mChildren; //迴圈子View for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index 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; } } } } return handled; }

省略了好些程式碼,這個其實分兩部分看,我用線畫出來了,上半部分,down事件會傳遞進來,然後執行onInterceptTouchEvent(ev); 這是攔截方法,Intercept是攔截的意思,預設執行結果是false,就是不攔截,而且這個方法是ViewGroup的內部方法不是繼承而來的,所以只有它有攔截,這個知識點主要是針對ViewGroup的父類——View,它是沒有這個方法的。其中的mFirstTouchTarget在down事件不攔截後就!=null,如果down事件被viewGroup攔截了,就會==null,intercepted就為true,之後的move、up等後續事件進不去那個if判斷,就不會分發給子view,自己消耗。

好,不攔截,然後往下走,橫線下面的if判斷中的canceled正常都是false,取反,intercepted又是false,好,進來了,下面的if正常也是會進去的(看變數名就可以知道一些),其中有一個for迴圈子view,馬上任務就要交給子view了,ViewGroup就歇會兒了。

重點看dispatchTransformedTouchEvent:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        //省...
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //省...
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        transformedEvent.recycle();
        return handled;
    }

程式碼要少一些,dispatchTouchEvent(分發)中的dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)傳遞的引數是ev、false、child、idBitsToAssign,也就是cancel永遠為false,child一般不為null,所以正常會呼叫child.dispatchTouchEvent方法,也就是View的dispatchTouhEvent方法,分發,這樣就從父佈局轉移到子view了。

public boolean dispatchTouchEvent(MotionEvent event) {
        final int actionMasked = event.getActionMasked();
       //省...
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
       //省...
        return result;
    }

走到這裡我們已經經歷兩個dispatchTouchEvent了,希望不要弄混,一個是ViewGroup的,一個是View的,雖然ViewGroup繼承自View,但是對佈局而言,我們要先執行父佈局(ViewGroup)的dispatchTouchEvent,然後在執行自己的dispatchTouchEvent,因為自己沒有這個方法,但自己的父佈局View是有的,所以會執行View的dispatchTouchEvent。

其中的if一般也是可以進來的,然後又是2個if判斷,第一個if判斷的result決定了第二個if判斷會不會執行,在本文中,因為沒有setOnTouchListener,所以第一個if進不來,就會到下一個if,也就會執行onTouchEvent方法:

 public boolean onTouchEvent(MotionEvent event) {
        //...省
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    //...省
                    if (!post(mPerformClick)) {
                         performClick();
                     } 
                    //...省
                    mIgnoreNextUpEvent = false;
                    break;
                case MotionEvent.ACTION_DOWN:
                    //...省
                    break;
                case MotionEvent.ACTION_CANCEL:
                    //...省
                    break;
                case MotionEvent.ACTION_MOVE:
                    //...省
                    break;
            }
            return true;
        }
        return false;
    }

當在DOWN事件中,其實從巨集觀角度沒什麼要特別解釋的,包括一些長按判斷等等,就省略了,重點是那個UP事件,當手指擡起的時候UP事件就會被觸發,它也會像down事件一樣經過層層篩選進來最後到view的onTouchEvent這裡,會執行performClick(),看一下程式碼:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        return result;
    }

這是所有程式碼,沒有省略,其它的也不用管,主要看if判斷中的一句,li.mOnClickListener.onClick(this);是不是看著眼熟?li.mOnClickListener就是我們設定的監聽,當Button設定了setOnClickListener後,我們看看它幹了什麼,首先:button繼承自TextView,TextView繼承自View,button和TextView都沒有這個方法,所以最後會執行view的setOnClickListener,看程式碼:

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);  //如果clickable為false就設為true
        }
        getListenerInfo().mOnClickListener = l;
    }

這樣就把我們設定的onClickListener物件賦值給getListenerInfo().mOnClickListener,當執行li.mOnClickListener.onClick(this);時就會執行我們寫的操作。

事件分發流程

其實前期可以把View和ViewGroup看成是兩個獨立的類,方便理解,但是很快就應該認識到兩者是繼承關係,並不獨立。

一句話說明就是:事件總會先傳給父元素,父元素進行分發(迴圈)和攔截判斷,不攔截就傳遞給子元素,子元素如果可以點選就會消耗事件,反之則返回給父元素處理。

正常情況下,down事件要經過這個迴圈(但沒有performClick),up事件也要走這個迴圈(有performClick),中間要是有move,就要走多次這個迴圈,挺繁瑣的,所以中間那些省略的程式碼中做了很多處理的,優化了操作步驟,比如:如果down事件被ViewGroup攔截了,整個事件序列要怎麼處理?每個方法都是有boolean型別的返回值的,這些返回值是幹嘛的?這些其實都在原始碼裡,包括我省略的部分,說一千,道一萬都不如自己看原始碼理解的透徹。