超过 2 个片段的解决方案。
(向下滚动以获取解决方案,最后更新代码以纠正一些错误。)
这个有点棘手。
我认为人们希望在一个方向上禁用滑动的原因,在我看来,是因为没有办法在运行时添加片段,同时保持显示的先前片段的状态。
所以人们正在做的是他们正在预加载所有的片段,并让那些没有展示的片段根本不存在。
现在,如果团队让适配器不受 ViewLifeCycle 的限制,这可以通过使用 ListDiffer 选项轻松解决,该选项可以正确地将更新传播到 RecyclerView 适配器,但是因为 ViewPager2 的每个 dataSetChanged 都需要一个新的适配器,需要重新创建整个Fragments,ListDiffer对ViewPager2没有影响。
但也许不完全是因为我不确定 ListDiffer 是否能够识别“位置交换”以保留状态。
现在,关于建议使用registerOnPageChangeCallback()
.
使用超过 2 个 Fragment 注册 OnPageChangeCallback() 的原因是,当调用此方法时,已经为时已晚,这会造成窗口在中途变得无响应,而不是addOnItemTouchListener(); 它能够在触摸到达视图之前拦截触摸。
在某种意义上,阻止和允许滑动的完整事务将由两个方法执行,registerOnPageChangeCallback() 和 addOnItemTouchListener()。
registerOnPageChangeCallback()
将告诉我们的适配器应该在哪个方向停止工作(通常从左到右(我将称之为“左”))以及在哪个页面,同时addOnItemTouchListener()
会告诉视图在我们想要的方向在正确的时刻截取投掷。
问题是要使用 TouchListener,我们需要访问 ViewPager2 中的内部 RecyclerView。
做到这一点的onAttachedToWindow()
方法是覆盖FragmentStateAdapter
.
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
现在调用了附加到 RecyclerView 的正确侦听器RecyclerView.SimpleOnItemTouchListener()
,问题是侦听器无法区分“右”弹跳和“左”弹跳。
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e)
我们需要混合 2 种行为来获得所需的结果:
一种)rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING
b)e.getX()
我们还需要跟踪最后一个 x 点,这样做的原因是监听器会在rv.getScrollState()
转弯前多次触发SCROLL_STATE_DRAGGING
。
解决方案。
我用来从左到右识别的类:
public class DirectionResolver {
private float previousX = 0;
public Direction resolve(float newX) {
Direction directionResult = null;
float result = newX - previousX;
if (result != 0) {
directionResult = result > 0 ? Direction.left_to_right : Direction.right_to_left ;
}
previousX = newX;
return directionResult;
}
public enum Direction {
right_to_left, left_to_right
}
}
事务后无需将 previousX int 置零,因为 resolve() 方法在rv.getScrollState()
变为之前至少执行了 3 次以上SCROLL_STATE_DRAGGING
一旦定义了这个类,整个代码应该是这样的(在里面FragmentStateAdapter
):
private final DirectionResolver resolver = new DirectionResolver();
private final AtomicSupplier<DirectionResolver.Direction> directionSupplier = new AtomicSupplier<>();
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.addOnItemTouchListener(
new RecyclerView.SimpleOnItemTouchListener(){
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
boolean shouldIntercept = super.onInterceptTouchEvent(rv, e);
DirectionResolver.Direction direction = directionSupplier.get();
if (direction != null) {
DirectionResolver.Direction resolved = resolver.resolve(e.getX());
if (rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
//resolved will never be null if state is already dragging
shouldIntercept = resolved.equals(direction);
}
}
return shouldIntercept;
}
}
);
super.onAttachedToRecyclerView(recyclerView);
}
public void disableDrag(DirectionResolver.Direction direction) {
Log.println(Log.WARN, TAG, "disableDrag: disabling swipe: " + direction.name());
directionSupplier.set(() -> direction);
}
public void enableDrag() {
Log.println(Log.VERBOSE, TAG, "enableDrag: enabling swipe");
directionSupplier.set(() -> null);
}
如果您询问 AtomicSupplier 是什么,它类似于 AtomicReference<> 所以如果您想使用它,它会给出相同的结果。这个想法是重用相同SimpleOnItemTouchListener()
的,为了做到这一点,我们需要为它提供参数。
我们需要检查空值,因为供应商第一次将为空(除非您为其提供初始值)recyclerView 首先附加到窗口。
现在使用它。
binding.journalViewPager.registerOnPageChangeCallback(
new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (conditionToDisableLeftSwipe.at(position)) {
adapter.disableDrag(DirectionResolver.Direction.right_to_left);
} else {
adapter.enableDrag();
}
}
}
}
);
更新
对 DirectionResolver.class 进行了一些更新,以解决 sme 错误和更多功能:
private static class DirectionResolver {
private float previousX = 0;
private boolean right2left;
public Direction resolve(float newX) {
Direction directionResult = null;
float result = newX - previousX;
if (result != 0) {
directionResult = result > 0 ? Direction.left_to_right : Direction.right_to_left;
} else {
directionResult = Direction.left_and_right;
}
previousX = newX;
return right2left ? Direction.right_to_left : directionResult;
}
public void reset(Direction direction) {
previousX = direction == Direction.left_to_right ? previousX : 0;
}
public void reset() {
right2left = false;
}
}
方向枚举:
public enum Direction {
right_to_left, left_to_right, left_and_right;
//Nested RecyclerViews generate a wrong response from the resolve() method in the direction resolver.
public boolean equals(Direction direction, DirectionResolver resolver) {
boolean result = direction == left_and_right || super.equals(direction);
resolver.right2left = !result && direction == left_to_right;
return result;
}
}
执行:
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.addOnItemTouchListener(
new RecyclerView.SimpleOnItemTouchListener(){
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
boolean shouldIntercept = super.onInterceptTouchEvent(rv, e);
Direction direction = directionSupplier.get();
if (direction != null) {
Direction resolved = resolver.resolve(e.getX());
if (rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
//resolved will never be null if state is already dragging
shouldIntercept = resolved.equals(direction, resolver);
resolver.reset(direction);
}
}
return shouldIntercept;
}
}
);
super.onAttachedToRecyclerView(recyclerView);
}
public void disableDrag(Direction direction) {
directionSupplier.set(() -> direction);
resolver.reset();
}
public void enableDrag() {
directionSupplier.set(() -> null);
}
采用:
binding.journalViewPager.registerOnPageChangeCallback(
new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (conditionToDisableLeftSwipe.at(position)) {
adapter.disableDrag(DirectionResolver.Direction.right_to_left);
} else {
adapter.enableDrag();
}
}
}
}
);