其实ViewPager对于触摸事件的分发已经做得非常好了,HorizontalScrollView以及使用了横向LinearLayoutManager的RecyclerView或者某些第三方banner轮播控件,基本没什么问题,能滚动到最后的,才会触发ViewPager的横向切换滑动。

但是在某些情况下,比如我这边使用场景是多个菜单栏,使用了第三方类似ViewPager的LayoutManager:PagerGridLayoutManager(https://xiaozhuanlan.com/topic/5841730926) 这个功能确实厉害。解决了List数据需要分页滑动显示问题。使用了这个的话,触摸就不太灵活了,必须是完全横向才能触发RecyclerView,不然触摸就会被ViewPager消费掉。

现在想要实现的是:触摸在菜单栏的RecyclerView上时,不会触发ViewPager的横向滚动。

 

在这之前,先看看这个问题普遍的解决办法:

1、把ViewPager设置成不响应滑动触摸,它就是一个纯粹的容器,也很容易实现:不拦截触摸,不消费触摸(public boolean onTouchEvent(MotionEvent ev) { return false; }    public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }   ) 很遗憾,这个办法被产品否决了。

2、使用NestedScrolling,ViewPager实现NestedScrollingParent。但是这个实现起来是有2个难点:NestedScrolling的2个接口Parent和Child是说,Child要滑动的时候,都会把自己的行动告诉Parent,Parent来判断是否需要消费,消费多少触摸距离。但是ViewPager是直接就拦截了触摸(之所以这样说,看底下),根本到不了底下的View。因此ViewPager的onInterceptTouchEvent需要大改,然后在onNestedPreScroll来判断是否消费滑动;第二个难点是对于我来说的,我的菜单栏是作为一个header放在另一个垂直滑动的RecyclerView内的,没试过套2层实现NestedScrolling,应该会有很多问题。

3、重写ViewPager的触摸了

 

本篇算是采用了第三种方法,但是比较简单。

理解这篇文章的前提,是需要对触摸事件分发有一点小小的了解。自上而下 自下而上    当前View onInterceptTouchEvent返回false的时候,事件继续向下传递,返回true,事件到当前view的onTouchEvent处理,onTouchEvent返回false则往上返回。因此假如一个ViewGroup包裹一个View 然后不设置onClick之类的,触摸View,事件从activity -> ViewGoup -> View ->  ViewGoup ->activity  这块不深究的话比较简单。

 

先写一下开始的思路:开始猜测的是,底层View的问题,也就是上面的RecyclerView。当然,其他控件都没问题,单单使用了PagerGridLayoutManager就有问题,肯定是PagerGridLayoutManager和ViewPager有冲突。因此我的目光看到了最下层的RecyclerView(下面用rvMenu代替)上,我的想法是rvMenu没完全消费完触摸事件,然后就到了上层的ViewPager上,因此写了个TouchView用来包裹rvMenu:

public class TouchView extends LinearLayout {    public TouchView(Context context) {        super(context);    }    public TouchView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        return true;    }}

不拦截触摸事件,因此触摸事件会继续往下传递,onTouchEvent返回true 把触摸事件都消费掉,这样ViewPager就接收不到触摸事件了(说一句,不要去动RecyclerView的触摸事件,因为它还是要传递给子View的,不能简单的返回true或者false)。想法很美好,结果并没有什么改变。因此可以得出上面说的结论:ViewPager是直接就拦截了触摸然后进行滑动

 

现在目光就转到了ViewPager上了:现在的思路是,当我触摸在rvMenu上时,onInterceptTouchEvent返回false,触摸其他位置时,默认不作处理,那么整体框架就出来了:

public class MyViewPager extends ViewPager {    public MyViewPager(Context context) {        super(context);    }    public MyViewPager(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {               if (是rvMenu的话) {             return false;        }        return super.onInterceptTouchEvent(ev);    }}

其实文章到这里就算结束了,判断返回false的时机,看具体情况。假如你是需要某个特定的ViewPager下的包裹的东西不响应的话,比如说在CFragment的时候横向滑动不响应,就可以这样写:

public class MyViewPager extends ViewPager {    public MyViewPager(Context context) {        super(context);    }    public MyViewPager(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Fragment fragment = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem());        if (fragment instanceof CFragment) {             return false;        }        return super.onInterceptTouchEvent(ev);    }}

我的情况是子View的子View,因此采用的是,MotionEvent的getRawX()和getRawY()来对比rvMenu的位置,如果是处于rvMenu的位置的话,返回false:

public class MyViewPager extends ViewPager {    public MyViewPager(Context context) {        super(context);    }    public MyViewPager(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        View v =  ((ViewAdapter) getAdapter()).getItem(getCurrentItem());        if (v instanceof MainFeedView) {            if (((MainFeedView) v).isOnTouch((int)ev.getRawX(), (int)ev.getRawY())) {                return false;            } else {                return super.onInterceptTouchEvent(ev);            }        }        return super.onInterceptTouchEvent(ev);    }}//MainFeedView里的isOnTouch方法:public boolean isOnTouch(int x, int y) {        return isTouchPointInView(rvMenu, x, y);    }//(x,y)是否在view的区域内    public static boolean isTouchPointInView(View view, int x, int y) {        if (view == null) {            return false;        }        int[] location = new int[2];        view.getLocationOnScreen(location);        int left = location[0];        int top = location[1];        int right = left + view.getMeasuredWidth();        int bottom = top + view.getMeasuredHeight();        //view.isClickable() &&        if (y >= top && y <= bottom && x >= left                && x <= right) {            return true;        }        return false;    }

 

更多相关文章

  1. Android基础考试知识点清单
  2. Android(安卓)Studio 4.1 更新内容
  3. Android事件机制(二)
  4. Android广播
  5. 使用getevent监听Android输入设备文件
  6. android 点击、滑动事件的处理 GestureDetector
  7. android 自定义图形之层叠样式 [layer-list] 的使用
  8. 给 Android(安卓)开发者的 Flutter 指南(上)
  9. Android(安卓)autotest - Monkey tool

随机推荐

  1. [转]Android(安卓)EditView属性
  2. Android(安卓)Activity onNewIntent() 详
  3. Android(安卓)NDK编程浅入深出之--了解ND
  4. android adb命令 抓取系统各种 log
  5. Android实现九宫格
  6. Android之MotionLayout(四),用 MotionLay
  7. android studio ndk 开发以及问题
  8. android 定时器的实现
  9. android 集成腾讯bugly版本升级功能
  10. eclipse 配置android sdk和maven