最近在做畢設(shè)的時(shí)候,一個(gè)頁(yè)面最外面套了一個(gè) ScrollView,內(nèi)部有多個(gè) ListView,然后發(fā)現(xiàn)在相同方向的滑動(dòng)總是會(huì)沖突,具體表現(xiàn)就是內(nèi)部的 ListView 完全不能滑動(dòng),只有當(dāng)一個(gè)手指按住非 ListView 區(qū)域,另外一個(gè)手指才能夠滑動(dòng) ListView 的內(nèi)容。這也是個(gè)典型的滑動(dòng)沖突場(chǎng)景,是《Android 開(kāi)發(fā)藝術(shù)探索》中提到的滑動(dòng)沖突場(chǎng)景二。
遇到這個(gè)問(wèn)題,一開(kāi)始想到的就是去翻閱開(kāi)發(fā)藝術(shù)探索這本書,然后書里推薦使用外部攔截法來(lái)解決沖突。實(shí)驗(yàn)了一下,發(fā)現(xiàn)并不是很方便。內(nèi)部攔截法需要層層判斷,而且還需要 HeaderView 來(lái)作為參考依據(jù),這樣在布局比較復(fù)雜的時(shí)候就沒(méi)那么簡(jiǎn)單了。既然這樣,那便考慮下內(nèi)部攔截法。
在 ListView 內(nèi)部做是否分發(fā)事件的判斷的時(shí)候,發(fā)現(xiàn)其實(shí)判別條件是很簡(jiǎn)單的,識(shí)別出滑動(dòng)的方向,然后如果內(nèi)部的 ListView 可以在該方向上滑動(dòng),那么就消費(fèi)該事件,否則交給上層 View 去處理。當(dāng)然,要如此判斷必須要求父層 View 不能攔截 Down 事件,也就是調(diào)用 requestDisallowInterceptTouchEvent(true) 方法,否則一直都接收不到后續(xù)事件了,而在不要的時(shí)候也需要調(diào)用 requestDisallowInterceptTouchEvent(false) 方法,讓事件走正常的處理流程。這樣的判斷可以一勞永逸,而且不需要引入其它參考 View,從代碼量上來(lái)說(shuō)也比較小。
下面是重寫 ListView 的代碼:
StickyListView.java
/**
* Created by Alpha on 2017/3/12.
*/
public class StickyListView extends ListView {
private float lastY;
public StickyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//確保 ScrollView 不會(huì)攔截按下事件
requestDisallowInterceptTouchEvent(true);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (lastY > ev.getY()) {//向上滑動(dòng)的時(shí)候
if (!canScrollList(1)) {
//如果 ListView 不能在這個(gè)方向上滑動(dòng)就把事件交給上層 ScrollView 處理
requestDisallowInterceptTouchEvent(false);
return false;
}
} else if (ev.getY() > lastY) {//向下滑動(dòng)的時(shí)候
if (!canScrollList(-1)) {
//如果 ListView 不能在這個(gè)方向上滑動(dòng)就把事件交給上層 ScrollView 處理
requestDisallowInterceptTouchEvent(false);
return false;
}
}
}
lastY = ev.getY();//記錄上次觸摸的位置
return super.dispatchTouchEvent(ev);
}
}
我用了一個(gè) lastY 來(lái)記錄手指開(kāi)始的位置,然后通過(guò)最后的位置來(lái)識(shí)別是向哪個(gè)方向的滑動(dòng),怎么樣,是不是很簡(jiǎn)單,當(dāng)然外部的 ScrollView 也需要稍微修改下:
StickyScrollView.java
/**
* Created by Alpha on 2017/3/12.
*/
public class StickyScrollView extends ScrollView {
public StickyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//將事件先傳遞給自身的觸摸響應(yīng),否則 ScrollView 自己的滑動(dòng)會(huì)受到影響
onTouchEvent(ev);
//這里直接返回 false 不會(huì)影響到自身,因?yàn)槿绻?View 沒(méi)有處理事件,那么最后事件還是會(huì)返還到這里的
return false;
}
return super.onInterceptTouchEvent(ev);
}
}
事實(shí)上 RecyclerView 內(nèi)部已經(jīng)在滑動(dòng)機(jī)制上處理得很好了,在外部嵌套 ScrollView 的情況下是不會(huì)出現(xiàn)滑動(dòng)沖突的。那為什么我還要使用 ListView 呢,我這里的需求只是展示幾個(gè)文字列表,內(nèi)容很少,不需要經(jīng)常刷新,考慮到以后的 App 發(fā)展情況,對(duì)于性能也沒(méi)有什么特別的需求,所以僅在易用性方面做考慮,那么肯定選擇 ListView 了,不需要設(shè)置 LayoutManager ,寫適配器也相對(duì)簡(jiǎn)單一些,這也是我使用 ListView 的原因。
本文最早發(fā)布于 http://alphagao.com/2017/03/13/the-solution-of-conflict-between-scrollview-and-inner-listview/