Android-SwipeRefreshLayout与ViewP
内部拦截法
1.ViewPager自定义实现
// ViewPager中的代码
//内部拦截法-需要父ViewGroup的配合
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 父ViewGroup一定不能拦截ACTION_DOWN事件
// 这个方法解决SwipeRefreshLayout嵌套ViewPager滑动冲突问题
ViewCompat.setNestedScrollingEnabled(this, true);
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 如果是水平滑动时,第一次move事件会进入,然后子View又设置
// requestDisallowInterceptTouchEvent(false);
// 这样又允许父ViewGroup进行拦截,当第二次move事件进入时
// 这样父ViewGroup中的dispatchTouchEvent中的intercepted=true
// 在父ViewGroup中的dispatchTouchEvent的末尾对cancelChild定义的时候
// final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
// 这样一来,子View就会进行取消事件的回调。
// 因为子View执行了cancel,所以mFirstTouchTarget = next,而next其实就是null
// 这里又置空了
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
2.SwipeRefreshLayout外部放开DOWN事件
// SwipeRefreshLayout中的代码
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
//内部拦截法
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
}
return true;
}
滑动实现问题
其实这样做,是有问题的,ViewPager并不能实现左右滑动的效果。
但是上面这样的做法,并不能完美实现拦截,ViewPager的左右滑动不能实现。而内部ViewPager的super.dispatchTouchEvent的确是返回了true,那么这里拦截不能实现,不能由ViewPager消化事件,则应该是getParent().requestDisallowInterceptTouchEvent方法没起作用。
但是因为SwipeRefreshLayout重写了requestDisallowInterceptTouchEvent,
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
// Nope.
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
正常情况下,||或条件的前半部分都会满足版本大于21,而我们这里的mTarget是ViewPager,所以并不是AbsListView,而||条件的后半部分,mTarget != null是满足的,而ViewCompat.isNestedScrollingEnabled(mTarget)其实内部实现如下:
public static boolean isNestedScrollingEnabled(@NonNull View view) {
if (Build.VERSION.SDK_INT >= 21) {
return view.isNestedScrollingEnabled();
}
if (view instanceof NestedScrollingChild) {
return ((NestedScrollingChild) view).isNestedScrollingEnabled();
}
return false;
}
从这里看出,其实就是会调用了ViewPager的isNestedScrollingEnabled()方法,其实就是调用了View.isNestedScrollingEnabled()方法,而ViewPager.isNestedScrollingEnabled()其实是返回了false,返回false,导致SwipeRefreshLayout重写了的requestDisallowInterceptTouchEvent是执行了if条件,而不是执行else,这样就什么都没执行
所以这里事件冲突的解决,其实还需要给ViewPager设置isNestedScrollingEnabled()为true
即调用:在内部拦截法的getParent().requestDisallowInterceptTouchEvent(true);之前
ViewCompat.setNestedScrollingEnabled(this, true);
解决方法二
还有一种方式,就是重写requestDisallowInterceptTouchEvent方法,在SwipeRefreshLayout中。
即在(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;的计算结果在move事件中要为true
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
Class clazz = ViewGroup.class;
try {
Field mGroupFlagsField = clazz.getDeclaredField("mGroupFlags");
mGroupFlagsField.setAccessible(true);
int c = (int) mGroupFlagsField.get(this);
Log.e("leo", "dispatchTouchEvent: c " + c);
if (b) {
// 这里的数值,可以采用没有冲突的代码,直接打印来用。
// 为true的时候,即mGroupFlags的值需要& FLAG_DISALLOW_INTERCEPT不为0
mGroupFlagsField.set(this, 2900051);
} else {
mGroupFlagsField.set(this, 2245715);
}
} catch (Exception e) {
e.printStackTrace();
}
// super.requestDisallowInterceptTouchEvent(b);
}