天天看點

android事件分發源碼分析—筆記

昨天晚上從源碼角度複習了一下android的事件分發機制,今天将筆記整理下放在網上。其實說複習,也是按着《android開發藝術探索》這本書作者的思路做的筆記。

目錄

  • 事件是如何從Activity傳遞到ViewGroup中的
  • ViewGoup對事件做了哪些操作
  • 事件在View中的處理

1. 事件是如何從Activity傳遞到ViewGroup中的

android的事件分發過程大緻為Activity-->ViewGroup-->View,當我們點選螢幕的時候産生事件,系統會調用的Activity

dispatchTouchEvent()

開始事件的傳播過程,下面我們看一下Activity的dispatchTouchEvent()源碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //當使用者操作了按鍵或者觸摸了螢幕,就會回調用該函數
        onUserInteraction();
    }
        //調用window的superDispatchTouchEvent()方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}           

Activity的dispatchTouchEvent()在它的ACTION_DOWN事件中調用

onUserInteraction()

這個函數可以用來監聽使用者的一些按鍵和觸摸操作。然後就會調用window的

superDispatchTouchEvent()方法

将事件傳播到window中,如果window的superDispatchTouchEvent()傳回true則代表事件被消耗了,反之代表事件沒有被處理,這個時候Activity就會調用自己的onTouchEvent()來處理。

  事件傳播到Window中該類是一個抽象類,它的superDispatchTouchEvent()方法是一個抽象方法。我們需要檢視它的實作類

PhoneWindow

中檢視事件的執行過程,下面為PhoneWindow的superDispatchTouchEvent()源碼:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //将事件傳遞給DecorView
    return mDecor.superDispatchTouchEvent(event);
}           

PhoneWindow的superDispatchTouchEvent()中就一行代碼,就是将事件傳遞給

頂層View

的superDispatchTouchEvent()。DecorView有将該方法傳遞給他父類

dispatchTouchEvent()

。如果讀者對DecorView有所了解,應該知道DecorView是FrameLayout的一個子類,也就是說事件傳遞到了ViewGroup的dispatchTouchEvent()方法中了。

2. ViewGoup對事件做了哪些操作

ViewGroup的dispatchTouchEvent()方法代碼很長,我們先看下面一本分代碼:

if (actionMasked == MotionEvent.ACTION_DOWN) {
    //将之前的狀态清除
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
   //預設情況下他們兩個比較的結果為0
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); 
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}           

從上面的代碼我們可以知道,如果ViewGroup分發的是ACTION_DOWN事件,那麼FLAG_DISALLOW_INTERCEPT這個标記位将會被重置,即使子View通過

requestDisallowInterceptTouchEvent()

設定了該标記為。也就是說分發下來的是ACTION_DOWN事件的話ViewGroup将會調用自己的

onInterceptTouchEvent()

尋問是否攔截。如果ViewGroup不攔截ACTION_DOWN事件,那麼事件将會向它的子View傳遞:

final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--){
    ...
    //通過dispatchTransformedTouchEvent()将事件傳遞給子View
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        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();
        //對mFirstTouchTarget指派
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }
    ev.setTargetAccessibilityFocus(false);
}           

ViewGroup首先會周遊子所有View,然後判斷子元素是否能接受這個點選事件。主要是通過兩點,子元素是否在播放動畫和點選事件的着落點是否在子元素的區域内。如果滿足上面的兩點那麼事件将會傳遞給他處理。

dispatchTransformedTouchEvent()

實際上就是子元素的dispatchTouchEvent()方法。

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}           

如果子元素的dispatchTounchEvnent()傳回的為true,那麼

mFirstTouchTarget

将會指派并且跳出循環,如果為false将會進入下次循環繼續周遊子View。

//對mFirstTouchTarget指派
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;           

下面為addTouchTarget()函數的代碼:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    //mFirstTouchTarget為連結清單結構
    mFirstTouchTarget = target;
    return target;
}           

mFirstTouchTarget是否指派,将會影響ViewGroup的攔截政策。如果mFirstTouchTarget為null,那麼ViewGroup将會攔截下來同一序列的所有事件。那mFirstTouchTarget在什麼情況下才為null呢?一般在兩種情況下,要麼是ViewGroup周遊了所有的子元素事件沒有被處理;要麼是子元素處理了ACTION_DOWN但是dispatchTouchEvent傳回為false。這兩種情況下ViewGroup會處理該事件,并且後續的事件也将交給他處理不再向子元素傳遞。

if (mFirstTouchTarget == null) {
    // 出入的第三個參數為null,代表事件交給ViewGroup自己處理
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}           

3. 事件在View中的處理

View的事件處理比ViewGroup要簡單,他首先會判斷是否設定是否設定了onTouchListener()函數。

下面為View的dispatchTouchEvent()部分代碼:

public boolean dispatchTouchEvent(MotionEvent event) {
     ...

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //首先判斷是否設定了onTouchListener()
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //調用onTouchEvent(event)
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
     ...
    return result;
}           

從上面代碼可知,如果我們設定了onTouchListener()那麼它會調用onTouch()方法,并且onTouch()傳回值将會影響對View的onTouchEvent(event)函數的調用,如果傳回true将不會調用。由此可見onTouch()的優先級要高于onTouchEvent()。

public boolean onTouchEvent(MotionEvent event) {
    ...
    //如果View設有代理,将會執行TouchDelegate.onTouchEvent(event)
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    //隻要View的CLICKABLE和LONG_CLICKABLE有一個傳回true,他就會被消耗這個事件。
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                  ...
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //點選事件
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    ...
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                ...
                //長安事件
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }
                ...
                break;
                ...
        }
        return true;
    }
    return false;
}           

從上面的代碼可以看出,View的點選事件是在ACTION_UP事件中調用了

performClick()

方法處理,長按事件是在ACTION_DOWN事件中調用了

checkForLongClick()

方法處理。

總結

文章到這裡也把android的分發機制從源碼的角度分析的差不多了,讀者應該對android的事件分發機制應該會有有一個比較全面的了解了。下篇文章我将記錄一下關于事件沖突的筆記,如果感興趣可以繼續關注。

參考

Android藝術開發探索第三章————View的事件體系(下)

繼續閱讀