$$滑动冲突
常见的场景有三种:
- 外部滑动与内部滑动方向不一致
- 外部滑动与内部滑动方向一致
- 前两种情况的嵌套
2.滑动冲突的处理规则
不同的场景有不同的处理规则,例如上面的场景一,规则一般就是当左右滑动时,外部View拦截事件,当上下滑动时要让内部View拦截事件,这时候处理滑动冲突就可以根据滑动是水平滑动还是垂直滑动来判断谁来拦截事件。场景而这种同个方向上的滑动冲突一般要根据业务逻辑来处理规则,什么时候要外部View拦截,什么时候要内部View拦截。场景三就更加复杂了,但是同样是根据具体业务逻辑,来判断具体的滑动规则。
3.滑动冲突的解决方法
- 外部拦截法
- 内部拦截法
外部拦截法是从父View着手,所有事件都要经过父View的分发和拦截,什么时候父View需要事件,就将其拦截,不需要就不拦截,通过重写父View的onInterceptTouchEvent
方法来实现拦截规则。
private int mLastXIntercept;
private int mLastYIntercept;
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;
}
按照以上伪代码,根据不同的拦截要求进行修改就可以解决滑动冲突。
内部拦截法的思想是父View不拦截事件,由子View来决定事件拦截,如果子View需要此事件就直接消耗掉,如果不需要就交给父View处理。这种方法需要配合requestDisallowInterceptTouchEvent
方法来实现。
private int mLastX;
private int mLastY;
@Override
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);
}
//父View的onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
这里父View不拦截ACTION_DOWN
方法的原因,根据之前的源码阅读可知如果ACTION_DOWN
事件被拦截,之后的所有事件就都不会再传递下去了。
scrollview嵌套HorizontalScrollView卡顿现象解决
开发中经验会遇到滑动里面嵌入滑动的问题,但是这种情况下触摸事件就会发生冲突。导致滑动非常卡,甚至出现程序停止响应。这种情况下我们一般需要重写view。下面给出重新scrollview的方法
public class CustomScrollView extends ScrollView { private GestureDetector mGestureDetector; View.OnTouchListener mGestureListener; public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); mGestureDetector = new GestureDetector(new YScrollDetector()); setFadingEdgeLength(0); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev); } // Return false if we're scrolling in the x direction class YScrollDetector extends SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if(Math.abs(distanceY) > Math.abs(distanceX)) { return true; } return false; } } }
ScrollView与ListView嵌套
首先自定义ScrollView,重写他的onInterceptTouchEvent
方法,拦击除了DOWN
事件以外的事件。
public class MyScrollView extends ScrollView {
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onTouchEvent(ev);
return false;
}
return true;
}
}
这里没有拦截DOWN
事件,所以DOWN
事件无法进入ScrollView的onTouchEvent
事件,又因为ScrollView的滚动需要在onTouchEvent
方法中做一些准备,所以这里手动调用一次。接着再自定义一个ListView,来决定事件拦截,重写dispatchTouchEvent
方法。
package com.example.sy.eventdemo;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ListView;
/**
* Create by SY on 2019/4/22
*/
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private float lastY;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (lastY > ev.getY()) {
// 这里判断是向上滑动,而且不能再向上滑了,说明到头了,就让父View处理
if (!canScrollList(1)) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
} else if (ev.getY() > lastY) {
// 这里判断是向下滑动,而且不能再向下滑了,说明到头了,同样让父View处理
if (!canScrollList(-1)) {
getParent().getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
}
}
lastY = ev.getY();
return super.dispatchTouchEvent(ev);
}
}
ViewPager与ListView嵌套
这个例子是水平和垂直滑动冲突。使用V4包中的ViewPager与ListView嵌套并不会发生冲突,是因为在ViewPager中已经实现了关于滑动冲突的处理代码,所以这里自定义一个简单的ViewPager来测试冲突。布局文件里就一个ViewPager:
看到现在只能上下滑动响应ListView的滑动事件,接着我们外部拦截发解决滑动冲突,核心代码如下:
case MotionEvent.ACTION_MOVE:
int gapX = x - lastX;
int gapY = y - lastY;
//当水平滑动距离大于垂直滑动距离,让父view拦截事件
if (Math.abs(gapX) > Math.abs(gapY)) {
intercept = true;
} else {
//否则不拦截事件
intercept = false;
}
break;
onInterceptTouchEvent
中当水平滑动距离大于垂直滑动距离,让父view拦截事件,反之父View不拦截事件,让子View处理。