在寫項目中,我們經常遇見一些滑動沖突的問題,是因為界面中內外兩層同時可以滑動造成的滑動沖突,但是要怎么解決呢?我結合我項目中遇到的實際問題來說明。
最外層是SwipeRefreshLayout,可以豎直滑動,內層是banner,可以左右滑動,當頁面處于頂部位置,左右滑動的時候也會觸發下拉刷新,造成頁面滑動沖突,那要怎么解決這個問題呢?
常見的滑動沖突類型有三種:
第一種:
外部滑動和內部滑動方向不一致;
第二種:
外部滑動和內部滑動方向一致;
第三種:
上面兩種情況的嵌套。
而我們上述案例的類型屬于第一種,他的處理規則是:
當用戶左右滑動的時候,需要讓內部view攔截點擊事件,當用戶上下滑動的時候,需要讓外部view攔截點擊事件。
那么我們就需要判斷用戶是水平方向滑動還是豎直方向滑動,
如上圖,根據兩個點的坐標就可以判斷出是水平滑動還是豎直滑動,這里我們就比較dx 和 dy的絕對值的大小,如果dx>dy 那么就是水平滑動否則為豎直滑動(當然判斷方式有很多種,這里就不一一說明)。根據這個規則我們就可以開始具體的解決方法了:
方法有兩種,
第一種,外部攔截法
是指點擊事件先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件就不攔截。
做法:重寫父容器的onInterceptTouchEvent方法,在內部做出相應的攔截即可,
具體實現代碼如下:
private static final String TAG = "MySwipelayout";
// 分別記錄上次滑動的坐標
private int mLastX = 0;
private int mLastY = 0;
private int x= 0;
private int y= 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
x = (int) event.getX();
y = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
mLastX = (int)event.getX();
mLastY = (int)event.getY();
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return false;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}
第二種,內部攔截法
因為外面包裹的是官方提供的控件,重寫父容器會相對復雜,那么這里我們就采用第二種方法,
父容器不做任何攔截,所有的事件全部都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則交由父容器進行處理,因為這種方法和android中事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,
(requestDisallowInterceptTouchEvent,見名知意,主要的效果就是讓控件的父控件不要調用onInterceptTouchEvent方法,并且不要攔截事件,這樣子控件就能拿到所有的事件,然后根據自己的邏輯進行處理)
內部攔截法我們只需要重寫dispatchTouchEvent方法:
因為SwipeRefreshLayout重寫了requestDisallowInterceptTouchEvent方法,所以以下代碼不適用于本案例,僅僅提供思路
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.youth.banner.Banner;
/**
* Created by jun zhu on 2017/8/3.
*/
public class MyBanner extends Banner {
public MyBanner(Context context) {
super(context);
}
public MyBanner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyBanner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 分別記錄上次滑動的坐標
private int mLastX = 0;
private int mLastY = 0;
private static final String TAG = "MyBanner";
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
requestDisallowInterceptTouchEvent(true);//讓控件的父控件不要調用onInterceptTouchEvent方法
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
if (Math.abs(deltaX) < Math.abs(deltaY)) {
requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
注意:除了子元素所做的處理以外,父元素也要默認攔截除了ACTION-DOWN 以外的事件,這樣當子元素調用requestDisallowInterceptTouchEvent方法時,父元素才能繼續攔截所需的時間