BottomSheetBehavior下ViewPager2的滑动问题
该布局结构下,ViewPager2下的RecyclerView无法响应滑动。
问题点
现象:BottomSheetBehavior可以正常折叠,展开,收起。展开状态下,滑动ViewPager2,无反应。BottomSheetBehavior可以正常响应事件,同时,事件被拦截无法传递到ViewPager2中。
为啥会被拦截?
在onInterceptTouchEvent的返回值,有这样一串逻辑:
View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
return action == MotionEvent.ACTION_MOVE
&& scroll != null
&& !ignoreEvents
&& state != STATE_DRAGGING
&& !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY())
&& viewDragHelper != null
&& Math.abs(initialY - event.getY()) > viewDragHelper.getTouchSlop();
isPointInChildBounds比较特殊,用于判断手势在不在"scroll"活动范围内。
nestedScrollingChildRef取值逻辑
在onLayoutChild中通过findScrollingChild赋值。
View findScrollingChild(View view) {
if (ViewCompat.isNestedScrollingEnabled(view)) {
return view;
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0, count = group.getChildCount(); i < count; i++) {
View scrollingChild = findScrollingChild(group.getChildAt(i));
if (scrollingChild != null) {
return scrollingChild;
}
}
}
return null;
}
通过判断isNestedScrollingEnabled来决定,并且是布局中的第一个。那应该就是布局中ViewPage2上方的RecyclerView了,将其nestedScrollingEnabled设为false试试看效果,结果ViewPage2部分可以上下滑动了,BottomSheetBehavior又不能展开了。根据对嵌套滑动的了解,RecyclerView在响应滑动事件的同时,也会将事件传递到父View中,也就意味着是BottomSheetBehavior响应的触发逻辑出了问题。onNestedPreScroll中有这样一段逻辑,判断是不是同一个view。
@Override
public void onNestedPreScroll(
@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child,
@NonNull View target,
int dx,
int dy,
@NonNull int[] consumed,
int type) {
...
View scrollingChild = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
if (target != scrollingChild) {
return;
}
...
}
target是真正滑动的view,即ViewPage2 item中的RecyclerView。而scrollingChild则成了ViewPager2的内部实现RecyclerViewImpl。从而导致BottomSheetBehavior不会同步滑动。那么就简单了,反射将RecyclerViewImpl的nestedScrollingEnabled关闭,就可以修正这一问题。
但随之而来一个新的问题,在切换tab的时候(也就是ViewPager2上方的RecyclerView),仍然会失效。原因也很简单,nestedScrollingChildRef仍然是ViewPager2中前一个item中的View。
如何修复?
重新触发nestedScrollingChildRef的赋值逻辑,因在layout的过程中赋值,只要requestLayout就可以达到目的。或者直接反射修改值。
总结
将BottomSheetBehavior中所有子View的isNestedScrollingEnabled设置false,除了需要响应滑动的RecyclerView。并在ViewPager2切换页面的时候,触发CoordinatorLayout.requestLayout()或反射修改nestedScrollingChildRef值。