美文网首页Android Dev
二十四、外部拦截、内部拦截方法解决View 的事件冲突

二十四、外部拦截、内部拦截方法解决View 的事件冲突

作者: 大虾啊啊啊 | 来源:发表于2021-07-06 14:54 被阅读0次

    一、前言

    分别使用外部拦截、内部拦截方法解决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事件

    相关文章

      网友评论

        本文标题:二十四、外部拦截、内部拦截方法解决View 的事件冲突

        本文链接:https://www.haomeiwen.com/subject/kajgultx.html