5.2View滑动冲突处理
目录介绍
- 01.什么是滑动冲突
- 02.外部拦截法
- 03.内部拦截法
- 04.滑动冲突实例
- 05.外部拦截法解决滑动冲突
- 06.内部拦截法解决滑动冲突
01.什么是滑动冲突
- 当父容器与子 View 都可以滑动时,就会产生滑动冲突。
- 解决 View 之间的滑动冲突的方法分为两种,分别是外部拦截法和内部拦截法
02.外部拦截法
- 父容器根据需要在
onInterceptTouchEvent
方法中对触摸事件进行选择性拦截,思路可以看以下伪代码public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.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; }
- 思路如下所示
- 根据实际的业务需求,判断是否需要处理 ACTION_MOVE 事件,如果父 View 需要处理则返回 true,否则返回 false 并交由子 View 去处理
- ACTION_DOWN 事件需要返回 false,父容器不能进行拦截,否则根据 View 的事件分发机制,后续的 ACTION_MOVE 与 ACTION_UP 事件都将默认交由父容器进行处理
- 原则上 ACTION_UP 事件也需要返回 false,如果返回 true,那么子 View 将接收不到 ACTION_UP 事件,子 View 的onClick 事件也无法触发
03.内部拦截法
- 内部拦截法则是要求父容器不拦截任何事件,所有事件都传递给子View,子View根据需求判断是自己消费事件还是传回给父容器进行处理,思路可以看以下伪代码:
- 子 View 修改其
dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此类点击事件) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
- 父容器修改其
onInterceptTouchEvent
方法
public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { return false; } else { return true; } }
- 子 View 修改其
- 思路所示
- 内部拦截法要求父容器不能拦截 ACTION_DOWN 事件,否则一旦父容器拦截 ACTION_DOWN 事件,那么后续的触摸事件都不会传递给子View
- 滑动策略的逻辑放在子 View 的
dispatchTouchEvent
方法的 ACTION_MOVE 事件中,如果父容器需要处理事件则调用parent.requestDisallowInterceptTouchEvent(false)
方法让父容器去拦截事件
04.滑动冲突实例
- 场景解释:
- 为了能使整个Activity界面能够上下滑动,使用了ScrollView,将Tablayout和ViewPager的联合包裹在LinearLayout中,作为一部分。
- 代码如下所示
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="300dp" android:background="@drawable/bg_autumn_tree_min"/> <include layout="@layout/include_reflex_view"/> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="50dp"/> <android.support.v4.view.ViewPager android:id="@+id/vp_content" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> </ScrollView>
- 遇到问题:
- 正常使用情况下,ViewPager中的Fragment没有显示出来,需要设置ViewPager的高度才可以,结果是ViewPager中的Fragment可以正常显示。
- 由于滑动方法不一样,导致滑动冲突。
05.外部拦截法解决滑动冲突
- 滑动方向不同之以ScrollView与ViewPager为例的外部解决法
- 从 父View 着手,重写 onInterceptTouchEvent 方法,在 父View 需要拦截的时候拦截,不要的时候返回false,代码大概如下
举例子:以ScrollView与ViewPager为例 public class MyScrollView extends ScrollView { public MyScrollView(Context context) { super(context); } public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(21) public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } private float mDownPosX = 0; private float mDownPosY = 0; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final float x = ev.getX(); final float y = ev.getY(); final int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mDownPosX = x; mDownPosY = y; break; case MotionEvent.ACTION_MOVE: final float deltaX = Math.abs(x - mDownPosX); final float deltaY = Math.abs(y - mDownPosY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (deltaX > deltaY) { return false; } } return super.onInterceptTouchEvent(ev); } }
06.内部拦截法解决滑动冲突
- 从子View着手,父View 先不要拦截任何事件,所有的 事件传递给子View,如果子View需要此事件就消费掉,不需要此事件的话就交给 父View 处理。
- 实现思路 如下,重写 子View 的dispatchTouchEvent方法,在Action_down动作中通过方法requestDisallowInterceptTouchEvent(true) 先请求 父View 不要拦截事件,这样保证子View能够接受到Action_move事件,再在Action_move动作中根据自己的逻辑是否要拦截事件,不要的话再交给 父View 处理
public class MyViewPager extends ViewPager { private static final String TAG = "yc"; int lastX = -1; int lastY = -1; public MyViewPager(Context context) { super(context); } public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getRawX(); int y = (int) ev.getRawY(); int dealtX = 0; int dealtY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: dealtX = 0; dealtY = 0; // 保证子View能够接收到Action_move事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: dealtX += Math.abs(x - lastX); dealtY += Math.abs(y - lastY); Log.i(TAG, "dealtX:=" + dealtX); Log.i(TAG, "dealtY:=" + dealtY); // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截 if (dealtX >= dealtY) { getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } lastX = x; lastY = y; break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); } }
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e