二十四、外部攔截、內部攔截方法解決View 的事件衝突

一、前言

分別使用外部攔截、內部攔截方法解決SwipeRefreshLayout+ViewPager事件衝突問題,主要衝突爲SwipeRefreshLayout是縱向滑動,而ViewPager是橫向滑動。雖然本身源碼已經做好了相應的處理,爲了鞏固事件分發的原理,我們通過自定義代碼的方法解決。
期望效果:橫向滑動ViewPager,縱向滑動SwipeRefreshLayout

二、外部攔截方法

package com.enjoy.srl_vp;

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import java.lang.reflect.Field;

public class MySwipeRefreshLayout extends SwipeRefreshLayout {
    private static final String TAG = "MySwipeRefreshLayout";

    public MySwipeRefreshLayout(Context context) {
        super(context);
    }

    public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private float startX;
    private float startY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
//            case MotionEvent.ACTION_DOWN:
//                startX = ev.getX();
//                startY = ev.getY();
//
//               return false;
            case MotionEvent.ACTION_MOVE:
                float delX = Math.abs(ev.getX() - startX);
                float delY = Math.abs(ev.getY() - startY);
                if (delY < delX) {
                    return false;
                }
//                //因爲在move事件的時候,雖然子View設置了,getParent().requestDisallowInterceptTouchEvent(false);
//                //但是往下滑動的時候我們發現在SwipeRefreshLayout的源碼中報了以下錯誤
//                //Got ACTION_MOVE event but don't have an active pointer id.
//                //報這個錯誤的原因是因爲 if (this.mActivePointerId == -1) 這個條件成立
//                //所以我們需要想辦法把這個屬性改掉,由於SwipeRefreshLayout沒有直接的API修改
//                //因此我想到了反射把mActivePointerId屬性改成0
//                if ((delY - delX) > 0) {
//                    Class clazz = getClass().getSuperclass();
//
//                    Field field = null;
//                    try {
//                        field = clazz.getDeclaredField("mActivePointerId");
//                        field.setAccessible(true);
//                        field.setInt(this, 0);
//                        Log.e(TAG, "onInterceptTouchEvent: 反射成功");
//                    } catch (Exception e) {
//                        Log.e(TAG, "onInterceptTouchEvent: 反射失敗" + e.getMessage());
//                        e.printStackTrace();
//                    }
//                }
//
//                break;

        }
        return super.onInterceptTouchEvent(ev);

    }
}

三、內部攔截方法

package com.enjoy.srl_vp;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class MyViewPager extends ViewPager {

    private static final String TAG = "MyViewPager";


    public MyViewPager(Context context) {
        super(context);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private float startX;
    private float startY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "子View: ACTION_DOWN");
                getParent(). requestDisallowInterceptTouchEvent(true);
                startX = ev.getX();
                startY = ev.getY();
                break;

            case MotionEvent.ACTION_MOVE:
                float delX = Math.abs(ev.getX() - startX);
                float delY = Math.abs(ev.getY() - startY);
                if (delX < delY) {
                    Log.e(TAG, "子View: 取消禁用攔截");
                    getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }

                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "子View: ACTION_UP");
                break;
            case MotionEvent.ACTION_CANCEL:
                Log.e(TAG, "子View: ACTION_CANCEL");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }


}

package com.enjoy.srl_vp;

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import java.lang.reflect.Field;

public class MySwipeRefreshLayout extends SwipeRefreshLayout {
    private static final String TAG = "MySwipeRefreshLayout";

    public MySwipeRefreshLayout(Context context) {
        super(context);
    }

    public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private float startX;
    private float startY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
               return false;
            case MotionEvent.ACTION_MOVE:
                float delX = Math.abs(ev.getX() - startX);
                float delY = Math.abs(ev.getY() - startY);
                //因爲在move事件的時候,雖然子View設置了,getParent().requestDisallowInterceptTouchEvent(false);
                //但是往下滑動的時候我們發現在SwipeRefreshLayout的源碼中報了以下錯誤
                //Got ACTION_MOVE event but don't have an active pointer id.
                //報這個錯誤的原因是因爲 if (this.mActivePointerId == -1) 這個條件成立
                //所以我們需要想辦法把這個屬性改掉,由於SwipeRefreshLayout沒有直接的API修改
                //因此我想到了反射把mActivePointerId屬性改成0
                if ((delY - delX) > 0) {
                    Class clazz = getClass().getSuperclass();

                    Field field = null;
                    try {
                        field = clazz.getDeclaredField("mActivePointerId");
                        field.setAccessible(true);
                        field.setInt(this, 0);
                        Log.e(TAG, "onInterceptTouchEvent: 反射成功");
                    } catch (Exception e) {
                        Log.e(TAG, "onInterceptTouchEvent: 反射失敗" + e.getMessage());
                        e.printStackTrace();
                    }
                }

                break;

        }
        return super.onInterceptTouchEvent(ev);

    }
}

四、小結

  • down事件只會分發一次,move會分發多次
  • 內部攔截思路是子View在dispatchTouchEvent方法中通過調用requestDisallowInterceptTouchEvent(true)方法,禁止父View攔截事件。並在合適的場景將其置爲fase允許攔截
  • 外部攔截思路是父View通過重寫onInterceptTouchEvent方法,在合適的場景攔截事件
  • 注意,如果是down事件,父親攔截了這個事件,那麼子View 將不在收到後續的事件,因此在解決事件衝突的時候,父親不能攔截down事件,而是在後續的move 事件在合適的場景做攔截,比如滑動的方向。
    當然如果需求是父親要直接攔截子View的事件,那麼也可以事件攔截掉down事件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章