我們爲了讓底部的控件處理事件,不被父控件攔截,一般我們會調用
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判斷的時候就會決定事件不再往下傳遞了