public boolean dispatchTouchEvent(MotionEvent ev){
boolean result =false;
if(onInterceptTouchEvent(ev)){
result=onTouchEvent(ev);
}
else {
result=child.dispatchTouchEvent(ev);
}
return result;
}
1.dispatchTouchEvent是進行事件分發;
2.onInterceptTouchEvent是攔截事件 ,攔截的意思是,如果攔截了,就不往子控件走,不會執行子控件的onTouchEvent。
3.onTouchEvent是消費點擊事件
如果onInterceptTouchEvent不攔截,那麼久看子控件的onInterceptTouchEvent是否會攔截並且onTouchEvent會調用,如果子控件onTouchEvent調用,那就沒有父控件的onTouchEvent什麼事,
僅針對ACTION_DOWN事件的傳遞
dispatchTouchEvent 和 onTouchEvent (向上)
return true,終結事件傳遞;
return false,事件都回傳給父控件的onTouchEvent處理
dispatchTouchEvent 返回值爲 false,意味着事件停止往子View分發,並往父控件回溯。
onTouchEvent 返回值爲 false,意味着不消費事件,並往父控件回溯。
onInterceptTouchEvent返回false,意味着繼續往子控件分發,
onInterceptTouchEvent返回ture,意味着攔截事件 不往子控件分發。
控件被點擊時,
onTouch返回false—>dispatchTouchEvent方法返回false—>執行onTouchEvent—>在performClick方法裏回調onClick
onTouch返回true—>dispatchTouchEvent方法返回true—>不執行onTouchEvent,顯然onClick方法也不會被調用
點擊事件的攔截機制,其實就是一個U型的模型。dispatchTouchEvent 和 onTouchEvent(如果執行到的話)爲false回溯父控件。onInterceptTouchEvent爲false分發給子控件。
onTouch的優先級高於onClick
控件被點擊時,
onTouch返回false—>dispatchTouchEvent方法返回false—>執行onTouchEvent—>在performClick方法裏回調onClick
onTouch返回true—>dispatchTouchEvent方法返回true—>不執行onTouchEvent,顯然onClick方法也不會被調用
進階
ACTION_MOVE和ACTION_UP相關
先來看看兩個實驗:
在View的dispatchTouchEvent 返回false並且在ViewGroup的onTouchEvent返回true
紅色的箭頭代表ACTION_DOWN事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP事件的流向
ViewDispatch_2
在ViewGroup 的onTouchEvent 返回true
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
ViewDispatch_03
總結一下:
如果在某個控件的dispatchTouchEvent 返回true消費終結事件,那麼收到ACTION_DOWN 的函數也能收到ACTION_MOVE和ACTION_UP。
在哪個View的onTouchEvent 返回true,那麼ACTION_MOVE和ACTION_UP的事件從上往下傳到這個View後就不再往下傳遞了,而直接傳給自己的onTouchEvent 並結束本次事件傳遞過程。
ACTION_DOWN事件在哪個控件消費了(return true), 那麼ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發往下傳,就只會傳到這個控件,不會繼續往下傳
如果ACTION_DOWN事件是在dispatchTouchEvent消費,那麼事件到此爲止停止傳遞
如果ACTION_DOWN事件是在onTouchEvent消費的,那麼會把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理並結束傳遞。
onTouch()和onTouchEvent()的區別
兩個方法都是在View的dispatchTouchEvent中調用,但onTouch優先於onTouchEvent執行。
如果在onTouch方法中返回true將事件消費掉,onTouchEvent將不會再執行。
View的dispatchTouchEvent方法中:
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
onTouch能夠執行需要的兩個前提:
mOnTouchListener不爲空
當前點擊的控件必須是ENABLED
因此如果你有一個控件是非enable的,那麼給它註冊onTouch事件將不會執行。
應用場景—滑動衝突的解決
滑動衝突在Android開發中一直都是一個痛點,之前的所有講解,就像是所有的招式,滑動衝突,就是我們的用武之地。
常見滑動衝突場景
外部滑動和內部滑動方向不一致
ViewPager和Fragment配合使用組成的頁面滑動效果。這種衝突的解決方式,一般都是根據水平滑動還是豎直滑動(滑動的距離差)來判斷到底是由誰來攔截事件。
外部滑動和內部滑動方向一致
內外兩層同時能上下滑動或者能同時左右滑動。這種一般都是根據業務來進行區分。
以上兩種場景的嵌套
滑動衝突的解決方式
外部攔截法
外部攔截法,就是所有事件都先經過父容器的攔截處理,由父容器來決定是否攔截。這種方式需要重寫父容器的onInterceptTouchEvent方法,僞代碼如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要當前點擊事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted=false;
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
幾點說明:
不攔截ACTION_DOWN事件。一旦父容器攔截ACTION_DOWN,則後續的ACTION_MOVE和ACTION_UP事件都會直接交由父容器處理,無法傳遞給子元素。
ACTION_MOVE事件根據具體需求來決定是否攔截。
ACTION_UP事件必須返回false,ACTION_UP事件本身沒什麼意義,但如果父容器在ACTION_UP返回true會導致子元素無法接收ACTION_UP事件,無法響應onClick事件。
內部攔截法
內部攔截法是指父容器不攔截任何事件,所有事件都傳遞給子元素。內部攔截法需要配合requestDisallowInterceptTouchEvent方法才能正常工作。這種方式需要重寫子元素的dispatchTouchEvent方法,僞代碼如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要當前點擊事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(ev);
}
父元素需要默認攔截除ACTION_DOWN事件以外的其他事件,父元素修改如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
ACTION_DOWN事件並不受FLAG_DISALLOW_INTERCEPT這個標記位的控制。一旦父容器攔截ACTION_DOWN事件,那麼所有的事件都無法傳遞到子元素中去。