關(guān)于安卓的事件分發(fā)機(jī)制,網(wǎng)上參考資料非常多,只有真正理解了事件分發(fā)的機(jī)制,才能很好的去處理自定義view過(guò)程中的事件沖突,事件分發(fā)機(jī)制參考資料,我認(rèn)為講的比較好
我的項(xiàng)目中當(dāng)然也遇見(jiàn)了事件沖突
如圖,項(xiàng)目中有一個(gè)橫向滑動(dòng)的自定義View,外面是RV,所以不處理肯定是這兩個(gè)view要沖突的,看下面的gif圖
事件沖突點(diǎn)
當(dāng)我在分時(shí)的view中長(zhǎng)按之后需要出來(lái)十字線(xiàn),且十字線(xiàn)是需要左右滑動(dòng)的,根據(jù)滑動(dòng)的點(diǎn)顯示當(dāng)前的信息
沖突點(diǎn)很明顯,就是當(dāng)我左右滑動(dòng)的時(shí)候,事件被最后那個(gè)子view(此處暫且就認(rèn)為就是我的分時(shí)圖的view)分發(fā),最終給到了外層的RV控件,所以RV控件認(rèn)為子view都不需要當(dāng)前的事件,它就自己處理了,所以造成在左右滑動(dòng)十字線(xiàn)的時(shí)候,RV上下滾動(dòng),分時(shí)View也就失去焦點(diǎn),十字線(xiàn)就消失了
解決思路
- 第一種,外部攔截法解決
即父View根據(jù)需要對(duì)事件進(jìn)行攔截。邏輯處理放在父View的onInterceptTouchEvent方法中。我們只需要重寫(xiě)父View的onInterceptTouchEvent方法允跑,并根據(jù)邏輯需要做相應(yīng)的攔截即可。
上一下偽代碼
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
//在此做x,y值的判斷,根據(jù)需求來(lái)選擇是否攔截事件
if (滿(mǎn)足父容器的攔截要求) {
intercept = true;
} else {
intercept = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
return intercepted;
}
常見(jiàn)的外部攔截法就是通過(guò)判斷event.getX()和event.getY()等距離,滿(mǎn)足我們的滑動(dòng)需求時(shí),去讓intercepted改變,從而達(dá)到父view不再向下傳遞事件,它自己消費(fèi)掉,最終來(lái)解決事件沖突
但是這種方法要注意以下兩點(diǎn)
- ACTION_DOWN 一定返回false获雕,不要攔截它薄腻,否則根據(jù)View事件分發(fā)機(jī)制收捣,后續(xù)ACTION_MOVE 與 ACTION_UP事件都將默認(rèn)交給父View去處理!
- 原則上ACTION_UP也需要返回false庵楷,如果返回true罢艾,并且滑動(dòng)事件交給子View處理,那么子View將接收不到ACTION_UP事件尽纽,子View的onClick事件也無(wú)法觸發(fā)咐蚯。而父View不一樣,如果父View在ACTION_MOVE中開(kāi)始攔截事件弄贿,那么后續(xù)ACTION_UP也將默認(rèn)交給父View處理春锋!
- 第二種,內(nèi)部攔截法
即父View不攔截任何事件,所有事件都傳遞給子View差凹,子View根據(jù)需要決定是自己消費(fèi)事件還是給父View處理期奔。這需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。
public boolean dispatchTouchEvent(MotionEvent event){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
if (父容器需要此類(lèi)點(diǎn)擊事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.dispatchTouchEvent(event);
}
這種方法同樣要注意
- 內(nèi)部攔截法要求父View不能攔截ACTION_DOWN事件危尿,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT標(biāo)志位控制呐萌,一旦父容器攔截ACTION_DOWN那么所有的事件都不會(huì)傳遞給子View。
- 滑動(dòng)策略的邏輯放在子View的dispatchTouchEvent方法的ACTION_MOVE中谊娇,如果父容器需要獲取點(diǎn)擊事件則調(diào)用 getParent().requestDisallowInterceptTouchEvent(false)方法肺孤,讓父容器去攔截事件。
綜上
很顯然,我需要針對(duì)自定義的View使用第二種方法,因?yàn)槲也豢赡苋ジ腞V的代碼,或者是自定義一個(gè)RV,當(dāng)長(zhǎng)按時(shí),自定義的子view消費(fèi)掉滑動(dòng)事件,不再傳遞給父view
思路就是這么個(gè)思路,在分時(shí)圖的View中做如下操作
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
if (isLongPress) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.dispatchTouchEvent(event);
}