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

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

目錄

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

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

android的事件分發過程大體爲Activity-->ViewGroup-->View,當咱們點擊屏幕的時候產生事件,系統會調用的ActivitydispatchTouchEvent()開始事件的傳播過程,下面咱們看一下Activity的dispatchTouchEvent()源碼:bash

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()來處理。ide

  事件傳播到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()方法中了。post

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傳遞:ui

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()方法。this

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}
複製代碼

若是子元素的dispatchTounchEvnent()返回的爲true,那麼mFirstTouchTarget將會賦值而且跳出循環,若是爲false將會進入下次循環繼續遍歷子View。spa

//對mFirstTouchTarget賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
複製代碼

下面爲addTouchTarget()函數的代碼:.net

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的事件體系(下)

相關文章
相關標籤/搜索