事件分發:事件的傳遞和傳遞路徑

事件傳遞方向

activity -> viewGroup -> view

 

事件的傳遞入口

事件觸發 -> 硬件 -> Native -> 通過JNI ->
Activity.dispatchTouchEvent() ->
PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() ->
ViewGroup.dispatchTouchEvent()

事件的開始

事件的觸發從手指觸摸屏幕開始,屏幕硬件接受到觸摸事件後,交由底層系統處理(native層)。native層再通過JNI通知Activity,觸發dispatchTouchEvent方法,開始事件在view中的一系列傳遞。

事件的傳遞

Activity對事件的傳遞最終是交給DecorView(頂層容器)處理,DecorView繼承自FrameLayout,也是一個ViewGroup。

事件的傳遞始終是在ViewGroup 和 View中逐層傳遞。

ViewGroup控制事件傳遞的3個方法

* dispatchTouchEvent;事件分發,向下傳遞事件
* onInterceptTouchEvent;事件攔截
* onTouchEvent;事件消費/處理

View控制事件傳遞的2個方法

* dispatchTouchEvent;事件分發,最終調用事件處理
* onTouchEvent;事件消費/處理

需要注意兩個dispatchTouchEvent方法:ViewGroup繼承自View,重寫了View的dispatchTouchEvent方法,主要功能是對 子View 或 子ViewGroup進行事件的傳遞;而View的dispatchTouchEvent方法,主要功能是調用事件處理的前期工作。

本文源碼版本,api:28

Activity中的傳遞路徑

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();//空方法,重寫用於事件觸發時被調用
    }
    //最終調用DecorView的dispatchTouchEvent()
      //即ViewGroup的dispatchTouchEvent()
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

Activity的dispatchTouchEvent()分兩種情況:
事件被消費,return true
沒有被消費,調用activity的onTouchEvent方法

ViewGroup中的傳遞路徑

//ViewGroup的dispatchTouchEvent()中 

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 {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

在事件分發的過程中,先獲取是否需要攔截,調用onInterceptTouchEvent()。

拓展點
disallowIntercept :不允許攔截的標記。
子view可以調用父容器的/requestDisallowInterceptTouchEvent/方法對其進行修改。

//ViewGroup的dispatchTouchEvent()中 
for (int i = childrenCount - 1; i >= 0; i—) {
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(
            preorderedList, children, childIndex);
。。。
//尋找可傳遞的子view代碼
。。。
      //重點1
    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();
          //重點2
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }
。。。
}

//——————————dispatchTransformedTouchEvent()——————————
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);
}
//——————————dispatchTransformedTouchEvent()——————————

遍歷查找可傳遞事件的子View。

重點1
在尋找到子View後,調用dispatchTransformedTouchEvent()。
子View如果爲空,就調用自己的父類方法
子View不爲空,就調用子View的事件分發,向下一層View傳遞事件。

子View繼續重複dispatchTouchEvent()方法,類似於遞歸方法的調用。
等待返回handled值。

舉個例子:當前有ViewGroupA,ViewGroupB,ViewA。ViewGroup包裹ViewGroupB,ViewGroupB包裹ViewA。

 

addTouchTarget方法

主要功能:記錄被消費的事件傳遞過程中每個view,形成完整的事件傳遞路徑。

下次的MOVE,UP事件等都只需要查找傳遞路徑即可分發事件,不必再重複查找View。

View的事件處理

//View的dispatchTouchEvent()
。。。
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;
}
。。。

首先判斷ListenerInfo是否爲空,ListenerInfo中包含各種點擊,雙擊,長按等監聽。所以這類監聽的優先級是高於onTouchEvent。

在沒有設置監聽的情況下,調用onTouchEvent方法,如果onTouchEvent方法進行了消費,返回true。

View的/dispatchTouchEvent/返回的true,被父容器ViewGroup的/dispatchTransformedTouchEvent/接收到,一路繼續向上傳遞,最終完成了事件路徑的記錄。

事件傳遞的完整路徑

 

總結

事件的傳遞迴顧

  • Activity接受事件,交給ViewGroup處理。
  • ViewGroup遍歷子View,遞歸查找被消費的子View。
  • 記錄遞歸handled=true的View,形成完整的事件傳遞鏈。
  • 其餘一系列事件直接經過 傳遞鏈 傳遞 事件。

ViewGroup的3個方法和View的2個方法之間的傳遞過程比較簡單。
關鍵點是ViewGroup的向下傳遞,遞歸查找消費子View。

最後

如果你看到了這裏,覺得文章寫得不錯就給個讚唄!歡迎大家評論討論!如果你覺得那裏值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術乾貨。喜歡的小夥伴可以關注一下哦。謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章