在我們平時項目開發(fā)的過程中,是不是會遇到滑動View之間的相互嵌套,比如外部的Scrollview或SwipeRefreshLayout嵌套內(nèi)部的Viewpager或橫向Recyclerview,如外部Viewpager嵌套內(nèi)部Viewpager。這樣往往就會造成滑動的沖突導(dǎo)致不流暢甚至根本滑不動。事件分發(fā)機制見我的另一篇Android事件分發(fā)機制,用事實說話。
滑動沖突產(chǎn)生的兩個根本原因:
- 外部滑動方向與內(nèi)部方向不一致
- 外部滑動方向與內(nèi)部方向一致
扎心了,一不一致都可能造成滑動沖突。第一種如ScrollView 嵌套ViewPager,第二種如ViewPager嵌套ViewPager。
解決方案:
既然叫攔截法,顧名思義就是需要重寫事件攔截方法,不管是外部攔截法還是內(nèi)部攔截法,都是重寫View的onInterceptTouchEvent,根據(jù)業(yè)務(wù)由不同的條件,來設(shè)置返回true、false還是super.onInterceptTouchEvent(ev)。
外部攔截法:
從父View著手,重寫onInterceptTouchEvent方法,在父View需要攔截的時候攔截事件,不需要則不攔截返回false。偽代碼如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(父View攔截條件){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
下面我們來看一個真實的案例:
SwipeRefreshLayout嵌套ViewPager,需要流暢橫向滑動Viewpager,這種操作下通常會順帶牽連到SwipeRefreshLayout致其拉動,如示例:
用外部攔截法自定義一個MySwipeRefreshLayout,在開始滑動的條件下,判斷手勢的橫縱方向的距離大小,判斷用戶到底是要橫向滑動還是縱向滑動。縱向滑動意為拉動刷新,就攔截事件,橫向滑動就意為滑動viewpager,就不攔截事件。解決效果:
MySwipeRefreshLayout代碼如下:
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public class MySwipeRefreshLayout extends SwipeRefreshLayout{
private float startX;
private float startY;
private float mTouchSlop;
public MySwipeRefreshLayout(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float distanceX = Math.abs(ev.getX() - startX);
float distanceY = Math.abs(ev.getY() - startY);
if(distanceX > mTouchSlop && distanceX > distanceY){ //判斷為橫向滑動
return false;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
getScaledTouchSlop()表示能觸發(fā)滾動的最小距離,如果小于這個距離就不觸發(fā)移動控件。
/**
* @return Distance in pixels a touch can wander before we think the user is scrolling
*/
public int getScaledTouchSlop() {
return mTouchSlop;
}
內(nèi)部攔截法:(getParent().requestDisallowInterceptTouchEvent(true),請求父容器不攔截事件)
從子View入手,重寫子元素的onInterceptTouchEvent方法,父View先不要攔截任何事件,所有的事件傳遞給子View,如果子View需要此事件就消費掉,不需要此事件的話就通過requestDisallowInterceptTouchEvent(false)方法交給父View處理。偽代碼如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(事件交給父View條件){
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onInterceptTouchEvent(ev);
}
父View需要重寫onInterceptTouchEvent:在ACTION_DOWN時設(shè)為不攔截子View事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
案例:Viewpager嵌套多個ViewPager:需求是默認(rèn)滑動內(nèi)部Viewpager,當(dāng)內(nèi)部Viewpager滑動到首頁或者末頁時,就滑動外部Viewpager切換頁面。解決效果:
自定義子Viewpager:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class InerViewPager extends ViewPager{
private int itemCount;
public InerViewPager(Context context) {
super(context);
}
public InerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
itemCount = getAdapter().getCount();
if(getCurrentItem() == 0 || getCurrentItem() == itemCount - 1){
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
外部view重寫:
public class OuterViewPager extends ViewPager {
public OuterViewPager(Context context) {
super(context);
}
public OuterViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
}
這里舉的是基本栗子,那如果產(chǎn)品經(jīng)理的想象力豐富,難度系數(shù)5.0以上呢?要是開發(fā)中遇到較復(fù)雜的滑動沖突,我只能說來一個殺一個,來兩個殺一雙!