

一、前言
????滑動(dòng)沖突在我們?nèi)粘?Android 開發(fā)中非常常見,當(dāng)我們單獨(dú)使用滾動(dòng)的滑動(dòng)控件(ScrollView 、ListView、ViewPager、RecyclView )時(shí)不會(huì)發(fā)生什么問題,但當(dāng)我們將他們結(jié)合起來時(shí),就會(huì)出現(xiàn)所謂的滑動(dòng)沖突問題。
二、滑動(dòng)沖突分類
滑動(dòng)沖突一般有三種場(chǎng)景:
- 外部滑動(dòng)與內(nèi)部滑動(dòng)方向不一致(比如 ViewPager 嵌套 ListView 時(shí))
- 外部滑動(dòng)與內(nèi)部滑動(dòng)方向 一致 (ScrollView 嵌套 ScrollView)
- 多層嵌套滑動(dòng)(就是將前面的兩種嵌套滑動(dòng)結(jié)合起來)
三、滑動(dòng)沖突處理思路
3.1、內(nèi)外滑動(dòng)方向不一致時(shí)處理思路
????這一類場(chǎng)景其實(shí)比較容易分析,因?yàn)橥鈱雍蛢?nèi)層滑動(dòng)的方向不一致,所以根據(jù)手勢(shì)的動(dòng)向來確定把事件給誰。默認(rèn)情況下,當(dāng)點(diǎn)擊內(nèi)層控件時(shí),事件會(huì)先一層層從外層傳到內(nèi)層,由內(nèi)層來處理。這里以外層為左右滑動(dòng),內(nèi)層為上下滑動(dòng)為例。當(dāng)判定手勢(shì)的滑動(dòng)為左右時(shí),需要外層來消費(fèi)事件,所以外層將事件攔截,即在外層的onInterceptTouchEvent中檢測(cè)為ACTION_MOVE時(shí)返回true;而如果判定手勢(shì)的滑動(dòng)為上下時(shí),需要內(nèi)層來消費(fèi)事件,外層不需要攔截,事件會(huì)傳遞到內(nèi)層來處理(具體的代碼實(shí)現(xiàn),在后面會(huì)詳細(xì)列出)。這樣就通過判斷滑動(dòng)的方向來決定事件的處理對(duì)象,從而解決滑動(dòng)沖突的問題。

3.2、內(nèi)外滑動(dòng)方向一致時(shí)處理思路
????這種場(chǎng)景要比上面一種復(fù)雜一些,因?yàn)榛瑒?dòng)方向一致,所以無法通過上述的方式來判斷將事件交給誰處理。在這種情況下,往往需要根據(jù)業(yè)務(wù)的需要來判定誰來處理事件。
????比如豎直方向的 ScrollView 嵌套 ScrollView 的場(chǎng)景下:
????手指在內(nèi)層 ScrollView 上下滑動(dòng)時(shí):事件需要被內(nèi)層 ScrollView 攔截,由內(nèi)層ScrollView 來消費(fèi);
????當(dāng)手指在外層 ScrollView 滑動(dòng)時(shí)事件需要被外層ScrollView 攔截,由外層 ScrollView來消費(fèi)。
3.3、多層滑動(dòng)嵌套時(shí)處理思路
????這種場(chǎng)景下看起來比較復(fù)雜,但其實(shí)是由前面兩種場(chǎng)景嵌套形成的。所以這種場(chǎng)景的處理方式,就是將其拆分為簡(jiǎn)單的場(chǎng)景,然后按照前面的場(chǎng)景分析方式來處理。
四、滑動(dòng)沖突兩種處理方法
4.1、外部攔截法
????顧名思義,就是在外部滑動(dòng)控件中處理攔截邏輯。這需要外部控件重寫父類的onInterceptTouchEvent 方法,在其中判斷什么時(shí)候需要攔截事件由自身處理,什么時(shí)候需要放行將事件傳給內(nèi)層控件處理,內(nèi)部控件不需要做任何處理。偽代碼如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要自己處理改事件) {
return true;//攔截
} else {
return false;//不攔截
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return super.onInterceptTouchEvent(ev);
}
4.2、內(nèi)部攔截法
????顧名思義,就是將事件是否需要攔截的邏輯,放到內(nèi)層控件中來處理。這種方式需要結(jié)合 requestDisllowInterceptTouchEvent(boolean) 這個(gè)方法,在內(nèi)層控件的重寫方法dispatchTouchEvent中,根據(jù)邏輯來決定外層控件何時(shí)需要攔截事件,何時(shí)需要放行。(requestDisllowInterceptTouchEvent 這個(gè)方法的作用是-是否允許外層控件攔截事件)偽代碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要處理改事件) {
//允許外層控件攔截事件
getParent().requestDisallowInterceptTouchEvent(false);
} else {
//需要內(nèi)部控件處理該事件,不允許上層viewGroup攔截
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
同時(shí),在這種方法下需要在外層控件做下處理:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
ACTION_DOWN事件仍然不能攔截,這是因?yàn)樵?ACTION_DOWN 事件時(shí)會(huì)初始化一些狀態(tài)和標(biāo)志位等變量導(dǎo)致 requestDisllowInterceptTouchEvent(boolean) 方法會(huì)失效。因此,在外層控件的 ACTION_DOWN 事件不能攔截。