解决方案,针对超过 2 个片段。
如果您已足够了解 Android,请直接查看代码;否则,请参考以下内容:
实际上,这个解决方案旨在模拟在给定方向上没有页面的情况。
因此,我建议使用下面的代码来禁用 ViewPager2 的滑动功能,并允许通过选项卡进行导航。可以添加或删除选项卡,使其看起来就像是正在添加或删除片段。
禁用整个 ViewPager2 的滑动功能,并允许仅通过选项卡进行导航。
可以添加或删除选项卡,使其看起来像是正在添加或删除片段。
为了使用 100% 功能完整的滑动功能,还需要解决至少两个行为问题,这些问题将在代码之后讨论。
代码
截至 2022 年 8 月 21 日,我终于花时间测试了一些代码问题,并想出了更好的解决方案:
public enum Direction {
allow_all(null),
right_to_left(Resolve.r2L()),
left_to_right(Resolve.l2R()),
left_and_right(Resolve.lR());
Direction(Resolve resolve) {
this.resolve = resolve;
}
@FunctionalInterface
private interface Resolve {
boolean resolve(float prev, float next);
static Resolve r2L() {
return (prev, next) -> prev > next;
}
static Resolve l2R() {
return (prev, next) -> prev < next;
}
static Resolve lR() {
return (prev, next) -> prev != next;
}
}
private final Resolve resolve;
public static class Resolver {
float prev;
long prevTime;
Direction toBlock = allow_all;
public boolean shouldIntercept(MotionEvent event) {
if (toBlock == allow_all) return false;
long nextTime = event.getDownTime();
float next = event.getX();
boolean intercept = false;
if (prevTime == nextTime) {
intercept = toBlock.resolve.resolve(prev, next);
} else {
prevTime = nextTime;
}
prev = next;
return intercept;
}
public void setToBlock(Direction toBlock) {
if (this.toBlock != toBlock) {
this.toBlock = toBlock;
prev = 0;
}
}
}
}
在适配器内部...
public class MyAdapter extends FragmentStateAdapter {
private final Direction.Resolver resolver = new Direction.Resolver();
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.addOnItemTouchListener(
new RecyclerView.SimpleOnItemTouchListener(){
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
return resolver.shouldIntercept(e);
}
}
);
super.onAttachedToRecyclerView(recyclerView);
}
public void disableDrag(Direction direction) {
resolver.setToBlock(direction);
}
public void enableDrag() {
resolver.setToBlock(Direction.allow_all);
}
}
缺点:
**
**
在页面切换时应执行setToBlock(Direction)方法。
问题是:谁来调用它/何时调用它?
我不知道答案......
我最好的猜测是将该方法放置在ViewPager的onPageSelected回调侦听器中是一个好地方.... 但是这个侦听器存在问题。
viewPager2.registerOnPageChangeCallback(
new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
if (position == 2) mAdapter.enableDrag(); return;
if (position == 1) mAdapter.disableDrag(left_to_right)
}
}
);
有时候,当使用某种手指快速滑动时,监听器在交换动画结束之前就会注册页面更改。这意味着在瞬间内,快速滑动会受到传入页面方向规则的影响,但实际上交换仍在前一页进行。
在上面的示例中,mAdapter.enableDrag(); 发生在位置2。假设位置0应该被禁止,因此位置1的规则是 mAdapter.disableDrag(left_to_right),以使位置0无法访问。
如果我用这种方式轻轻地滑动手指,使ViewPager注册一个位置更改到2(eanbleDrag()),然后朝相反方向滑动而不离开手指,则页面从半渲染的位置2返回到不可访问的位置0。
这不难重现,每5次尝试中就有1次,但您需要积极想要重现它。
我不知道如何解决这个问题。
也许应该在交换的后期才调用disableDrag(),但这意味着要访问页面片段的生命周期。这意味着使用setTargetFragment()方法(“干净”的方法),而我宁愿死也不用那个。
另一种选择是使用绑定到ViewPagerFrag到PageFrag的后退堆栈条目的共享ViewModel进行Fragment到Fragment通信。
当然...暂且不管这两种方法都在幕后使用静态字段进行引用存储...这意味着您完全可以采用公共静态方式,当然前提是保持代码整洁...
B)限制一个方向的滑动。
实际上,这个解决方案旨在模仿给定方向上页面的缺席,问题在于,如果我们仔细分解页面缺席的行为,我们会注意到动画只有在达到给定轴之后才限制运动,更准确地说,一旦最后一页(索引0)完全显示,限制就成为现实,如果索引0的页面向索引1滑动并稍微偏移屏幕,您仍然可以向0滑动页面。
通过限制一个方向的移动,并让此规则统治整个页面,会出现不需要的行为:
例如:[位置0(禁止)] - [1(允许)] - [2(允许)]
为了模拟位置0的缺席,位置1必须disableDrag(left_to_rigth);
如果我们从1向2拖动手指,然后再轻轻地拖回来(可能是因为用户改变主意决定留在页面1)...,那么,由于整个页面受到toBlock方向==left_to_rigth的支配,页面将拒绝返回,并且动画将卡在两个片段之间。
我的猜测是,在给定的Fragment Y轴达到屏幕上的给定触发Y轴时(即:使用屏幕坐标监听器),应该执行禁用操作。这意味着我们需要更多了解组件提供给我们访问的所有不同可用的监听器。
...
所以...
目前我能做的最好的就是,我真的很希望能得到任何关于如何解决这个问题或者处理主要问题的建议,即添加和删除Fragment而不会丢失状态。尽管我几乎看不到这种可能性(至少在StateAdapter中),因为DiffUtil需要推断重新排序的更改,而我认为Mayer的算法并不适用于处理那种级别的推断(重新排序推断(“索引跳跃”)。相反,Mayers仅通过推断整个段重新排序来工作。
此外,Fragment集合需要同时作为LIFO和FIFO(我认为术语是旋转木马?),以支持从两端添加和删除,顶部(禁止从左到右访问)和底部(禁止从右到左访问)。
我认为这太过具体和繁琐,不是ViewPager2工具必要的增强功能。
因为我缺乏所有这些细节的知识...我认为最好的解决方案是:
禁用整个ViewPager2的滑动功能,只允许通过选项卡进行导航。然后可以添加或删除选项卡,使其看起来像是添加或删除了Fragment。