今天來介紹下Android必備的知識點之一——滑動沖突。《Android開發藝術探索》書中有做了詳細闡述,為了方便各位看官,我會介紹如何處理滑動沖突。
- 場景重現
- 解決思路
- 范例
場景重現
外部滑動方向和內部滑動方向不一致,這種場景常見如ViewPager和內嵌ListView的Fragment組合。ViewPager內部處理了這種滑動沖突,但如果外層用ScrollView來實現的話那么就需要我們自行處理這種滑動沖突。
外部滑動方向和內部滑動方向一致。這種場景常見如ListView內部項嵌套左右滑動的圖片瀏覽列表,與外部ViewPager滑動沖突。
上述兩種場景的嵌套。這種場景常見如ViewPager和復雜的ListView,ListView內部項嵌套左右滑動的圖片瀏覽列表,ListView上下滑動,外部ViewPager左右滑動
解決思路
場景一:當用戶左右滑動時,需要讓外部的View攔截點擊事件,當用戶上下滑動時,需要讓內部View攔截點擊事件。可以依據滑動路徑與水平方向的夾角、水平方向與豎直防線上的距離差或者水平與豎直方向的速度差來做判斷。
場景二:這種就不能用場景一的思路來處理,可以依據業務上的規定,如內部滑動ListView在最頂層等等,當用戶處于某種狀態時需要外部View響應用戶的滑動,而處于另外一種狀態時需要內部View的滑動。
場景三:滑動規則也無法直接根據滑動的角度、距離差以及速度差來做判斷,同樣只能在業務上找到突破點。
接下來具體介紹幾種通用處理思路
外部攔截法
點擊事件都經由父容器攔截處理,需要重寫父容器的onInterceptTouchEvent方法,在方法內部做相應攔截即可。
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;//不攔截
if(!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if(Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;//x軸攔截距離
mLastYIntercept = y;//y軸攔截距離
return interceped;
}
內部攔截法
父容器不攔截任何事件,所有事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則交由父容器進行處理,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(Math.abs(deltaX) > Math.abs(deltaY)) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
//父容器onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action = MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
范例
下面附上一個實例來介紹這兩種方法,實現一個類似于ViewPager中嵌套ListView的效果。為了制造沖突環境,我們需要重寫一個類似ViewPager的自定義控件HorizontalScrollViewEx(繼承ViewGroup,需要重寫onMeasure和onLayout方法)。實際上就是根據這兩種方法來處理的,HorizontalScrollViewEx1使用外部攔截法;HorizontalScrollViewEx2使用內部攔截法。需要的同學可以自取代碼(截取Android開發藝術探索)。
https://github.com/singwhatiwanna/android-art-res/tree/master/Chapter_3