滑动冲突可以分为三类
- 外部滑动方向和内部滑动方向不一致
- 场景:类似viewpager嵌套listview的情况
- 处理思路:直接根据逻辑来就好,(外左右内上下时)当用户左右滑动,需要让外部view拦截事件,上下滑动就让内部view拦截事件
- 具体方法:根据滑动的特征是水平滑动还是竖直滑动来判断到底由谁来拦截事件
- 如何根据滑动过程两点间的坐标得到滑动方向?有好几个思路
- 根据滑动路径和水平方向的夹角
- 根据水平方向和竖直方向上的距离差[比较方便]
- 还可以根据水平和竖直方向的速度差
- 拦截事件:事件分发
- 如何根据滑动过程两点间的坐标得到滑动方向?有好几个思路
- 外部滑动方向和内部滑动方向一致
- 处理思路:
- 这个时候根据逻辑来行不通,用户滑动的时候不知道用户到底是想让哪一层滑动;
- 但是这个时候一般能在业务上找到突破点,根据业务需求得到具体的处理规则
- 处理思路:
- 上面两种情况嵌套
- 场景:外部有一个slidemenu,内部有一个嵌套了listview的viewpager
- 处理思路:
- 虽然是前面两种情况的叠加,看起来跟复杂,但是只要分开处理内层和中层,中层和外层之间的冲突,逐个击破就好了,具体的处理方式是和场景1、2相同的
- 通用也是无法根据滑动特征来判断,只能从业务入手
本质上说这三类的复杂度是相同的,区别只是解决滑动冲突的策略不同,具体解决的方法是通用的
不依赖滑动规则(距离差/角度/逻辑/业务)的通用的解法
1.外部拦截法[建议用这种方法]
- 所有的点击事件都先经过父容器拦截处理,如果父容器需要拦截就拦截,不需要就传给内部的View
- 这种方法符合点击事件的分发机制
外部拦截法的典型逻辑,重写父view 的onInterceptTouchEvent 方法即可:
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; switch (event.getAction()) { } |
其中:
- 针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其他均不需做修改并且也不能修改
- ACTION_DOWN这个事件是不能拦截的,因为一旦拦截后续的事件都会由父容器处理了,无法再传递给子view
- 应该在ACTION_MOVE中根据需求决定是否拦截
- 最后ACTION_UP必须返回false,不拦截,因为这个事件本身没太多意义,但返回true会引发问题:
- 假如在ACTION_MOVE中把事件交给子view处理了,但是如果在ACTION_UP中又返回true,就会导致子view无法收到ACTION_UP事件,于是子view的onclick事件就会无法触发
2.内部拦截法
- 父容器不拦截任何事件,所有事件都传给子元素。如果子元素需要此事件就直接消耗,否则就交给父容器进行处理。
- 因为这种方法和android中的事件处理流有些不一致(因为...),完成这个功能需要配合requestDisallowInterceptTouchEvent()方法才可,这个方法表示是否让父容器拦截事件
重写子view的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { |
- 同样,针对不同的滑动策略,只需要修改这个条件即可,其他均不需做修改并且也不能修改
除了子view需要处理外,父view也要拦截除了ACTION_DOWN以外所有的事件
- 这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)时,父元素才能拦截所需的事件
- 不能拦截ACTION_DOWN的原因是
- ACTION_DOWN事件不受这个标记位控制,所以一旦父view拦截这个事件,那么所有的事件都无法传递到子view中,这样内部拦截就无法起作用了
父view的修改:
public boolean onInterceptHoverEvent(MotionEvent event) { int action = event.getAction(); if(action == MotionEvent.ACTION_DOWN){ return false; }else { return true; } } |
以场景1为例做了外部拦截法讲解
场景2,3的做法与1类似,只不过是根据业务需要制定处理规则
这个例子
- 实现了一个类似viewpager嵌套listview的效果
- 不过把viewpager改成了自己写的一个HorizontalScrollViewEx,这样就制造了滑动冲突,使用滑动距离差了解决冲突
- 这是一个类似水平linearlayout的东西,不过它可以水平滑动
[1]activity
public class DemoActivity_1 extends Activity { @Override private void initView() { private void createList(ViewGroup layout) { ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.content_list_item, R.id.name, datas); } } |
[2]水平滑动的View 其中:
public class HorizontalScrollViewEx extends ViewGroup { private int mChildrenSize; // 分别记录上次滑动的坐标 private Scroller mScroller; public HorizontalScrollViewEx(Context context) { public HorizontalScrollViewEx(Context context, AttributeSet attrs) { public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) { private void init() { @Override switch (event.getAction()) { if (!mScroller.isFinished()) { Log.d(TAG, "intercepted=" + intercepted); mLastX = x; return intercepted; @Override int x = (int) event.getX(); switch (event.getAction()) { mVelocityTracker.computeCurrentVelocity(1000); //滑的速度到达阈值就认为需要进入下一页 //保证在0页和最后一页滑动时不会越界 //没有达到进入下一页的要求,恢复原样 Log.d(TAG, "onTouchEvent: dx = " + dx); mLastX = x; private void smoothScrollBy(int dx, int dy) { @Override @Override int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); @Override for (int i = 0; i < childCount; i++) { @Override |
参考资料:
《安卓开发艺术探索》