超过 2 个片段的解决方案。
(向下滚动查看解决方案,最后更新代码以纠正一些错误。)
这个有点棘手。
我认为,我认为人们希望在一个方向上禁用滑动的原因是因为无法在运行时添加片段,同时保持之前显示的片段的状态。
所以人们正在做的是他们正在预加载所有片段,并让那些没有显示的片段根本不存在。
现在,如果团队让适配器不受 ViewLifeCycle 的限制,这可以通过使用 ListDiffer 选项轻松解决,该选项可以正确地将更新传播到 RecyclerView 适配器,但是因为每个 dataSetChanged 都需要一个新的适配器ViewPager2,需要重新创建整个Fragments,ListDiffer对ViewPager2没有影响。
但也许不完全是因为我不确定 ListDiffer 是否能够识别“位置交换”以保留状态。
现在,关于建议使用registerOnPageChangeCallback() 的答案。
使用超过 2 个 Fragment 来 registerOnPageChangeCallback() 没有用的原因是,当这个方法被调用时,做某事已经太晚了,这造成的是窗口在中途变得无响应,与 addOnItemTouchListener() 不同;它能够在触摸到达视图之前拦截它们。
从某种意义上说,阻止和允许滑动的完整事务将由 registerOnPageChangeCallback() 和 addOnItemTouchListener() 这两个方法执行。
registerOnPageChangeCallback() 将告诉我们的适配器应该在哪个方向停止工作(通常从左到右(我将称之为“左”))以及在哪个页面,而addOnItemTouchListener() 会告诉视图在在我们想要的方向上的正确时刻。
问题在于,要使用 TouchListener,我们需要访问 ViewPager2 中的内部 RecyclerView。
执行此操作的方法是从 FragmentStateAdapter 覆盖 onAttachedToWindow() 方法。
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
现在附加到 RecyclerView 的正确侦听器称为 RecyclerView.SimpleOnItemTouchListener(),问题是侦听器无法区分“右”抛物和“左”抛物。
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e)
我们需要混合两种行为来获得想要的结果:
a)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() 变为SCROLL_STATE_DRAGGING 之前至少执行了3 次以上
一旦定义了这个类,整个代码应该是这样的(在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();
}
}
}
}
);