1. 程式人生 > >Android 滑動衝突的解決方法

Android 滑動衝突的解決方法

一、常見的滑動衝突場景

場景1——外部滑動方向和內部滑動方向不一致,如:ViewPager中有多個fragment,而fragment中有ListView,這時ViewPager可以左右滑動,而ListView可以上下滑動,這就造成了滑動衝突。注意:這只是舉個例子說明一下場景1,事實上ViewPager內部已經處理了這種滑動衝突,在採用ViewPager時,我們無需關注這個問題。

場景2——外部滑動方向和內部滑動方向一致。

場景3——上述兩種場景的巢狀,即共有三層,外層與中層的滑動方向一致,而中層與內層的滑動方向不一致。

二、滑動衝突的處理規則

場景1的處理規則: 1、當用戶左右滑動時,讓外部的View攔截點選事件,當用戶上下滑動時,讓內部的View攔截點選事件; 2、判斷使用者的滑動方向(左右、上下):如果使用者手指滑動的水平距離大於垂直距離,則左右滑動,反之,上下滑動;還可以根據角度、速度差來做判斷;

場景2的處理規則: 無法根據滑動的角度、距離差、速度差來判斷,因為場景2內部、外部的滑動方向一致;這時候一般都能在業務上找到突破點,如業務上規定:當處於某種狀態需要外部View響應使用者的滑動,而處於另一種狀態時則需要內部View響應使用者的滑動,所以我們可以根據業務的需求得出相應的處理規則。

場景3的處理規則:場景1的處理規則和場景2的處理規則一起用。

三、滑動衝突的解決方法

以下兩種解決方法都是通用的方法:1、外部攔截法 我們都知道點選事件是先經過父容器的攔截處理的,所以我們就可以在父容器的攔截方法中寫下自己的邏輯程式碼即可,相當於我們要重寫一個佈局Layout,使其繼承ViewGroup或其它的佈局(LinearLayout等),然後重寫其onInterceptTouchEvent方法。外部攔截法的虛擬碼如下:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int)ev.getX();
        int y = (int)ev.getY();

        switch (ev.getAction()){

            //父容器不需要攔截
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;

            //在此判斷父容器是否需要攔截
            case MotionEvent.ACTION_MOVE:
                if (父容器需要當前點選事件){
                    intercepted = true;
                }
                //父容器不需要當前的點選事件
                else {
                    intercepted = false;
                }
                break;

            //父容器不需要攔截
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }

        //重置手指的起始位置
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

解釋上述程式碼: 1、父容器不攔截ACTION_DOWN事件:那是因為一旦父容器攔截了ACTION_DOWN事件,後續的ACTION_MOVE、ACTION_UP事件都會直接交給父容器處理,這個時候事件無法傳遞給子元素了。

2、父容器不攔截ACTION_UP事件:首先我們要知道onClick 事件是在ACTION_UP事件之後執行的,那當子元素有一個onClick事件,而這時候父容器攔截了ACTION_UP事件,那子元素的onClick事件就無法執行了。

3、ACTION_MOVE事件:在這裡可以根據我們的需求,來判斷父容器是否需要攔截事件,需要則返回true,否則返回false。

總結,外部攔截法比較簡單,實現起來較容易。

2、內部攔截法 首先我們瞭解下ViewGroup.requestDisallowInterceptTouchEvent(boolean)方法,此方法就是在子View中通知父容器攔截或不攔截點選事件,false ——攔截,true ——不攔截;

內部攔截法的思想是父容器先不攔截任何事件,即所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交由父容器進行處理。這時我們就用到requestDisallowInterceptTouchEvent 方法了。

具體做法:我們重寫子View的dispatchTouchEvent方法,即我們新建一個類繼承子View,然後重寫它的dispatchTouchEvent方法,虛擬碼如下:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //獲得當前的位置座標
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:

                //通知父容器不要攔截事件
                horizontalScrollLayout.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:

                if (父容器需要此事件){
                    //通知父容器攔截此事件
                    horizontalScrollLayout.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }

        //重置手指的初始位置
        mLastY = y;
        mLastX = x;

        return super.dispatchTouchEvent(ev);
    }
  • 程式碼: 1、horizontalScrollLayout是此子View的父容器的物件,我們可以在子 View中定義一個方法:
public void setHorizontalScrollLayout(HorizontalScrollLayout horizontalScrollLayout) {
        this.horizontalScrollLayout = horizontalScrollLayout;
    }

此時,父容器攔截了除ACTION_DOWN以外的其它事件,那為什麼這樣做? 原因:只有這樣,才能使的當子元素呼叫了requestDisallowInterceptTouchEvent(false)方法後,父容器才能繼續攔截所需的事件。

3、那父容器為什麼不能攔截ACTION_DOWN事件呢? 原因:因為ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT這個標記位的控制,所以一旦父容器攔截了ACTION_DOWN事件,那麼所有的事件都無法傳遞給子元素了,這樣內部攔截就起不到作用了。

總結,內部攔截法比較複雜,在實際應用中,建議還是用外部攔截法。

四、舉例說明

在舉例的時候,我自定義了幾個view,大家主要看其中的dispatchTouchEvent方法和onInterceptTouchEvent方法,至於自定View的方法會在之後的部落格中詳細描述。

場景1的解決方案:外部攔截和內部攔截的方法都有 。程式碼下載請點我

--------------------- 作者:struggle323 來源:CSDN 原文:https://blog.csdn.net/struggle323/article/details/50788136