Решение для более чем двух фрагментов. (прокрутите вниз, чтобы найти решение, в конце обновите код, чтобы исправить некоторые ошибки.)
Это было немного сложно.
Я думаю, что причина, по которой люди хотят отключить смахивание в одном направлении, на мой взгляд, заключается в том, что нет возможности добавлять фрагменты во время выполнения, сохраняя при этом состояние предыдущих фрагментов на дисплее.
Итак, что люди делают, так это то, что они предварительно загружают все фрагменты и делают так, как будто тех, которые не отображаются, просто нет.
Теперь, если бы команда сделала адаптер, не ограниченный ViewLifeCycle, это можно было бы легко решить, используя параметр ListDiffer, который правильно распространял бы обновления на адаптер RecyclerView, но потому что новый адаптер требуется для каждого dataSetChanged ViewPager2. необходимо воссоздать все фрагменты, а ListDiffer не влияет на ViewPager2.
Но, возможно, не совсем, поскольку я не уверен, что ListDiffer может распознать обмен позиции для сохранения состояния.
Теперь об ответе, который советует использовать registerOnPageChangeCallback()
.
Причина, по которой бесполезно регистрироватьOnPageChangeCallback () с более чем двумя фрагментами, потому что к тому времени, когда этот метод вызывается, уже слишком поздно что-то делать, это создает то, что окно перестает отвечать на запросы на полпути, в отличие от addOnItemTouchListener (); который способен перехватывать касания до того, как они достигнут поля зрения.
В некотором смысле полная транзакция блокировки и разрешения пролистывания будет выполняться двумя методами, registerOnPageChangeCallback () и addOnItemTouchListener ().
registerOnPageChangeCallback()
Сообщит нашему адаптеру, в каком направлении следует перестать работать (обычно слева направо (я назову это просто left)) и на какой странице, а addOnItemTouchListener()
сообщит представлению, что нужно перехватить бросок в нужный момент в нужном нам направлении.
Проблема в том, что для использования TouchListener нам нужно получить доступ к внутреннему RecyclerView внутри ViewPager2.
Для этого нужно переопределить метод 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 поведения, чтобы получить желаемый результат:
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 () выполняется как минимум более 3 раз, прежде чем rv.getScrollState()
станет 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 для учета ошибок и дополнительных функций:
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();
}
}
}
}
);
person
Delark
schedule
15.12.2020