requestDisallowInterceptTouchEvent實現原理

我們爲了讓底部的控件處理事件,不被父控件攔截,一般我們會調用

v.getParent().requestDisallowInterceptTouchEvent(true);

來阻止父控件對事件的攔截,來看下它的實現原理。

首先明確下v.getParent()對於底部的View來說,得到的就是上層的父控件,也就是上層的ViewGroup,來看下ViewGroup的requestDisallowInterceptTouchEvent方法

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

首先就是判斷是否已經設置過,如果沒有,則在mGroupFlags中添加FLAG_DISALLOW_INTERCEPT這個標記位,接着如果該控件還有父控件,則一層一層在往上傳遞。
也就是說調用該方法就是將其上層所有控件的mGroupFlags中添加上FLAG_DISALLOW_INTERCEPT標記位。
那是怎麼起到讓父控件不攔截事件呢,我們知道事件的分發主要由三個方法決定dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent,我們看下ViewGroup的dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent ev) {
            ......
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            // Check for interception.
            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;
            }
            ......
}

首先看下resetTouchState方法

private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

關注點就是mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;也就是說如果是事件的起點,即MotionEvent.ACTION_DOWN的話就將標記位清除
接着就是關鍵點,決定是否調用onInterceptTouchEvent方法
我們看到第一個if中的判斷語句爲

actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null

也就是說必須滿足至少一個條件纔有可能去執行onInterceptTouchEvent方法
1:事件爲ACTION_DOWN事件
2:mFirstTouchTarget 不爲空
我們看下mFirstTouchTarget 的說明

    // First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;

就是說mFirstTouchTarget 代表這個ViewGroup下第一個處理了事件的控件
要麼爲ACTION_DOWN事件,要麼底下的控件處理了控件,反言之,如果這個ViewGroup曾經把事件交給下面的View去處理,而下面的View沒有處理的話,那麼下次事件ViewGroup的intercepted就直接被賦值爲true,即將事件攔截,自己處理,好吧,這個很重要,但是和我們的requestDisallowInterceptTouchEvent關係也是不大
接着看第二個if判斷語句

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;
}

disallowIntercept就是前面我們一直提到的標記位
如果disallowIntercept爲false,也就是默認值,那麼就會走正常的onInterceptTouchEvent去判斷是否攔截,像RelativeLayout,LinearLayout這樣的一般不需處理事件的ViewGroup一般都會返回false,事件還是會繼續傳遞下去;但是像RecyclerView,ViewPager這些,他們往往會根據判斷事件的具體情況,決定是否攔截,可能自己就將事件消費了
而如果disallowIntercept爲true的話,即我們設置不允許父控件攔截事件,那麼他的onInterceptTouchEvent方法就不會執行,intercepted直接設置爲false,即不攔截事件,事件會傳遞給下層的View,當然瞭如果最終下面的View不爭氣,沒有處理事件的話,那麼根據我們上面說的,之後的事件在第一個if判斷的時候就會決定事件不再往下傳遞了

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