注意:
- 閱讀本文需要了解《Android事件分發(fā)機(jī)制》
- 在此知識(shí)點(diǎn)歹颓,本人也有部分困惑尚未完全解決,也會(huì)在文中標(biāo)出出來(lái)尚困。
常見的滑動(dòng)沖突場(chǎng)景及對(duì)應(yīng)的處理規(guī)則
- 外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致
面對(duì)這種情況的滑動(dòng)沖突昧旨,解決規(guī)則是:根據(jù)滑動(dòng)是水平滑動(dòng)還是豎直滑動(dòng)來(lái)判斷由誰(shuí)來(lái)攔截事件查乒。判斷滑動(dòng)方向的方法是:比較水平方向和豎直方向滑動(dòng)距離的大小盯拱,或者滑動(dòng)路徑和水平方向的夾角盒发,或者根據(jù)水平方向和豎直方向的速度差。 - 外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致
這種情況的滑動(dòng)沖突狡逢,無(wú)法根據(jù)滑動(dòng)的角度判斷宁舰,一般都是根據(jù)業(yè)務(wù)需要來(lái)進(jìn)行判斷。 - 上面兩種情況的結(jié)合
這種情況比較復(fù)雜奢浑,一般也需要從業(yè)務(wù)上找到突破點(diǎn)明吩。
場(chǎng)景一
場(chǎng)景一:假如打算做個(gè)像ViewPager一樣的效果,父容器如horizonal方向的LinearLayout一樣殷费,容納了三個(gè)ListView。
外部攔截法
就是指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理低葫,如果不需要此事件就不攔截详羡,這樣就可以解決滑動(dòng)沖突的問題。外部攔截法需要重寫父容器的onInterceptTouchEvent()
方法嘿悬,在內(nèi)部完成相應(yīng)的攔截即可实柠。
public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted = false;
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false; //注解1
if (!mScroller.isFinished()){ //注解2
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE: //注解3
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYintercept;
if (Math.abs(deltaX) > Math.abs(deltaY)){ //在這里的if中加入是否攔截的判斷
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = fasle; //注解4
break;
default:
break;
}
mLastXIntercept = x;
mLastYintercept = y;
return intercepted;
}
注解1:
為什么要在ACTION_DOWN時(shí),intercepted = false
善涨?因?yàn)槿绻贏CTION_DOWN時(shí)窒盐,intercepted == true
草则,那么根據(jù)Android事件分發(fā)機(jī)制,后面的MOVE事件和UP都會(huì)無(wú)條件的交給父容器去處理蟹漓。這樣的話炕横,事件永遠(yuǎn)無(wú)法傳遞給子View。
此處的困惑:如果僅僅在父容器中設(shè)置ACTION_DOWN時(shí)葡粒,intercepted = false
還是不夠的份殿。intercepted = false
,然后DOWN事件傳遞給了子View嗽交,按照Android事件分發(fā)機(jī)制卿嘲,如果子View沒有成功處理DOWN事件(即返回了false),最終還是會(huì)調(diào)用父容器的處理方法夫壁。如果這樣的話拾枣,后面的MOVE事件和UP依然會(huì)無(wú)條件的交給父容器去處理。
注解2:
這個(gè)if內(nèi)的語(yǔ)句盒让,針對(duì)的是下面的情況:如果用戶此時(shí)在進(jìn)行父容器的滑動(dòng)方向(這里是水平滑動(dòng))梅肤,但是在水平滑動(dòng)之前如果用戶再迅速進(jìn)行豎直滑動(dòng),就會(huì)導(dǎo)致界面在水平方向無(wú)法滑動(dòng)到終點(diǎn)從而處于一種中間狀態(tài)糯彬。為了避免這種情況凭语,當(dāng)水平滑動(dòng)時(shí),下一個(gè)序列的點(diǎn)擊事件仍然交給父容器處理(哪怕豎直方向滑動(dòng)距離大于水平方向滑動(dòng)距離撩扒,此時(shí)仍然判定是水平滑動(dòng))似扔。
注解3:
這一塊代碼是解決滑動(dòng)沖突的關(guān)鍵。在MOVE事件中搓谆,判斷這一滑動(dòng)事件是水平滑動(dòng)還是豎直滑動(dòng)炒辉,如果是水平滑動(dòng),作為父容器就攔截事件泉手,如果是水平滑動(dòng)黔寇,就不攔截事件,交給子view去處理斩萌。
此處的困惑:如果在一系列的MOVE事件中缝裤,前部分是水平移動(dòng),后部分是豎直移動(dòng)的颊郎,那怎么辦憋飞?因?yàn)闆]有攔截DOWN事件,所有很有可能事件攔截過程中的mFirstTouchTarget != null
姆吭,所以后部分的MOVE事件仍然要調(diào)用onInterceptTouchEvent()
榛做,此時(shí),intercept = false;
,那么接著交給子View處理检眯?
如何解答這個(gè)困惑呢厘擂?有一個(gè)結(jié)論是:
一旦父容器開始攔截任何一個(gè)事件,那么后續(xù)的事件都會(huì)交給它來(lái)處理锰瘸。
這個(gè)結(jié)論先記住吧刽严,暫時(shí)還沒有搞明白為什么會(huì)這樣。
注解4:
如果父容器在UP事件中返回了true获茬,就會(huì)導(dǎo)致子View無(wú)法接受到UP事件港庄,這個(gè)時(shí)候子元素中的onClick
事件就無(wú)法處罰法。同樣的恕曲,
因?yàn)橐坏└溉萜鏖_始攔截任何一個(gè)事件鹏氧,那么后續(xù)的事件都會(huì)交給它來(lái)處理,所以UP作為最后一個(gè)事件也必定可以傳遞給父容器佩谣,即便父容器的
onInterceptTouchEvent
方法在UP時(shí)返回了false
但是依然沒有弄明白為什么有這個(gè)結(jié)論把还。
內(nèi)部攔截法
內(nèi)部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素茸俭,如果子元素需要此事件就直接消耗掉吊履,否則就交由父容器進(jìn)行處理。這種方法需要配合requestDisallowInterceptTouchEvent()
方法才能正常工作调鬓。
第一步艇炎,修改父容器的onInterceptTouchEvent()
,讓其在DOWN事件返回false腾窝,其他情況下返回true缀踪。
//父容器內(nèi)
public boolean onInterceptTouchEvent(MotionEvent event){
int x = (int)event.getX();
int y = (int)event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN){ //注解5
mLastX = x;
mLastY = y;
if (!mScroller.isFinished()){
mScroller.abortAnimation()
return true;
}
return true;
} else {
return true;
}
}
注解5:
父容器攔截了除了DOWN事件以外的其他事件,這樣當(dāng)子元素調(diào)用parent.requestDisallowInterceptTouchEvent(false)
方法時(shí)虹脯,父元素才能繼續(xù)攔截所需的事件驴娃。
第二步,修改子元素的dispatchTouchEvent()
方法
//在子View中
public boolean dispatchTouchEvent(MotionEvent event){
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
XXX.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)){ //在這里的if中加入是否攔截的判斷
XXX.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event); //注解6
}
注解6:因?yàn)樽觱iew是自定義view循集,重寫的dispatchTouchEvent()
方法唇敞,在解決了滑動(dòng)沖突后,調(diào)用父類的dispatchTouchEvent()
方法來(lái)進(jìn)行原來(lái)的事件分發(fā)咒彤。
場(chǎng)景二和場(chǎng)景三
在總體的實(shí)現(xiàn)方法和場(chǎng)景一是一樣的疆柔,僅僅是在MOVE事件中判斷的條件不一樣,場(chǎng)景一僅僅是通過滑動(dòng)方向來(lái)進(jìn)行判斷镶柱,而場(chǎng)景二和場(chǎng)景三需要判斷業(yè)務(wù)邏輯婆硬。這里就不詳細(xì)介紹了。