Android滑動沖突的解決方式

在寫項目中,我們經常遇見一些滑動沖突的問題,是因為界面中內外兩層同時可以滑動造成的滑動沖突,但是要怎么解決呢?我結合我項目中遇到的實際問題來說明。

滑動沖突示例圖.gif

最外層是SwipeRefreshLayout,可以豎直滑動,內層是banner,可以左右滑動,當頁面處于頂部位置,左右滑動的時候也會觸發下拉刷新,造成頁面滑動沖突,那要怎么解決這個問題呢?

常見的滑動沖突類型有三種:
第一種:
外部滑動和內部滑動方向不一致;
第二種:
外部滑動和內部滑動方向一致;
第三種:
上面兩種情況的嵌套。

而我們上述案例的類型屬于第一種,他的處理規則是:
當用戶左右滑動的時候,需要讓內部view攔截點擊事件,當用戶上下滑動的時候,需要讓外部view攔截點擊事件。
那么我們就需要判斷用戶是水平方向滑動還是豎直方向滑動,

滑動過程.png

如上圖,根據兩個點的坐標就可以判斷出是水平滑動還是豎直滑動,這里我們就比較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);
    }
最終效果圖.gif

第二種,內部攔截法

因為外面包裹的是官方提供的控件,重寫父容器會相對復雜,那么這里我們就采用第二種方法,
父容器不做任何攔截,所有的事件全部都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則交由父容器進行處理,因為這種方法和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方法時,父元素才能繼續攔截所需的時間

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容