Страница-слушатель изменила прослушиватель в PagerSnapHelper - PullRequest
0 голосов
/ 29 августа 2018

Я использую PagerSnapHelper в горизонтальном RecyclerView, чтобы добиться пейджерного поведения.

final PagerSnapHelper pagerSnapHelper = new PagerSnapHelper(); pagerSnapHelper.attachToRecyclerView(recyclerView);

Это прекрасно работает, но я хочу иметь возможность получать обратные вызовы, когда пользователь изменяет страницу в любом направлении. Так что-то вроде onSwipeLeft / onSwipeRight обратных вызовов.

Я попытался использовать findTargetSnapPosition в PagerSnapHelper, но это дает мне targetIndex, а не текущий индекс. Я пытался что-то вроде этого, но это не всегда работает.

@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
    final int targetPos = super.findTargetSnapPosition(layoutManager, velocityX, velocityY);

    final View currentView = findSnapView(layoutManager);
    final int currentPos = layoutManager.getPosition(currentView);

    if (currentPos < targetPos) {
        callback.onSwipeRight();
    } else if (currentPos > targetPos) {
        callback.onSwipeLeft();
    }

    return targetPos;
}

Есть ли лучший способ добиться этого, который всегда работает? Спасибо!

1 Ответ

0 голосов
/ 27 марта 2019

Обновление

В исходном классе были некоторые проблемы с уведомлением о первом макете. Теперь он срабатывает только в первый раз, когда позиция предмета меняется с RecyclerView.NO_POSITION на что-то другое.

Чтобы далее распространяться на игнорирование / срабатывание только пользовательских жестов, поэтому при непрограммных вызовах scrollTo() обратите внимание, что onScrolled() запускается с dx == 0 and dy == 0 в случае программного вызова.

public class SnapPagerScrollListener extends RecyclerView.OnScrollListener {

    // Constants
    public static final int ON_SCROLL = 0;
    public static final int ON_SETTLED = 1;

    @IntDef({ON_SCROLL, ON_SETTLED})
    public @interface Type {
    }

    public interface OnChangeListener {
        void onSnapped(int position);
    }

    // Properties
    private final PagerSnapHelper snapHelper;
    private final int type;
    private final boolean notifyOnInit;
    private final OnChangeListener listener;
    private int snapPosition;

    // Constructor
    public SnapPagerScrollListener(PagerSnapHelper snapHelper, @Type int type, boolean notifyOnInit, OnChangeListener listener) {
        this.snapHelper = snapHelper;
        this.type = type;
        this.notifyOnInit = notifyOnInit;
        this.listener = listener;
        this.snapPosition = RecyclerView.NO_POSITION;
    }

    // Methods
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if ((type == ON_SCROLL) || !hasItemPosition()) {
            notifyListenerIfNeeded(getSnapPosition(recyclerView));
        }
    }

    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (type == ON_SETTLED && newState == RecyclerView.SCROLL_STATE_IDLE) {
            notifyListenerIfNeeded(getSnapPosition(recyclerView));
        }
    }

    private int getSnapPosition(RecyclerView recyclerView) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager == null) {
            return RecyclerView.NO_POSITION;
        }

        View snapView = snapHelper.findSnapView(layoutManager);
        if (snapView == null) {
            return RecyclerView.NO_POSITION;
        }

        return layoutManager.getPosition(snapView);
    }

    private void notifyListenerIfNeeded(int newSnapPosition) {
        if (snapPosition != newSnapPosition) {
            if (notifyOnInit && !hasItemPosition()) {
                listener.onSnapped(newSnapPosition);
            } else if (hasItemPosition()) {
                listener.onSnapped(newSnapPosition);
            }

            snapPosition = newSnapPosition;
        }
    }

    private boolean hasItemPosition() {
        return snapPosition != RecyclerView.NO_POSITION;
    }
}

Оригинальный ответ

Поскольку сборка отсутствует, вам необходимо создать собственный класс. Идея состоит в том, чтобы прослушать методы RecyclerViews onScrolled()/onScrollStateChanged() и при необходимости уведомить ваши обратные вызовы.
Методы интерфейса частично совпадают, и для моего использования я удалил методы onSwipeLeft/Right(). Это только для демонстрации.

public class SnapPagerScrollListener extends RecyclerView.OnScrollListener {

    // Constants
    public static final int ON_SCROLL = 0;
    public static final int ON_SETTLED = 1;

    @IntDef({ON_SCROLL, ON_SETTLED})
    public @interface Type {
    }

    public interface OnChangeListener {
        void onSnapped(int position);

        void onSwipeRight();

        void onSwipeLeft();
    }

    // Properties
    private final PagerSnapHelper snapHelper;
    private final int type;
    private final boolean notifyOnFirstLayout;
    private final OnChangeListener listener;
    private int snapPosition;

    // Constructor
    public SnapPagerScrollListener(PagerSnapHelper snapHelper, @Type int type, boolean notifyOnFirstLayout, OnChangeListener listener) {
        this.snapHelper = snapHelper;
        this.type = type;
        this.notifyOnFirstLayout = notifyOnFirstLayout;
        this.listener = listener;
        this.snapPosition = RecyclerView.NO_POSITION;
    }

    // Methods
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if ((type == ON_SCROLL) || notifyOnFirstLayout) {
            notifyListenerIfNeeded(getSnapPosition(recyclerView));
        }
    }

    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (type == ON_SETTLED && newState == RecyclerView.SCROLL_STATE_IDLE) {
            notifyListenerIfNeeded(getSnapPosition(recyclerView));
        }
    }

    private int getSnapPosition(RecyclerView recyclerView) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager == null) {
            return RecyclerView.NO_POSITION;
        }

        View snapView = snapHelper.findSnapView(layoutManager);
        if (snapView == null) {
            return RecyclerView.NO_POSITION;
        }

        return layoutManager.getPosition(snapView);
    }

    private void notifyListenerIfNeeded(int newSnapPosition) {
        if (snapPosition != newSnapPosition) {
            if (!isFirstLayout() || notifyOnFirstLayout) {
                listener.onSnapped(newSnapPosition);
            }

            if (!isFirstLayout()) {
                if (snapPosition > newSnapPosition) {
                    listener.onSwipeLeft();
                } else if (snapPosition < newSnapPosition) {
                    listener.onSwipeRight();
                }
            }

            snapPosition = newSnapPosition;
        }
    }

    private boolean isFirstLayout() {
        return snapPosition == RecyclerView.NO_POSITION;
    }
}


Использование:
Просто добавьте экземпляр SnapPagerScrollListener в свой RecyclerView

your_recycler_view.addOnScrollListener(new SnapPagerScrollListener(your_snap_helper, SnapPagerScrollListener.ON_SCROLL/ON_SETTLED, true/false, your_on_changed_listener));



Согласно документу RecyclerView.onScrolled()

Этот обратный вызов также будет вызываться, если диапазон видимых элементов изменяется после вычисления макета. В этом случае dx и dy будут равны 0.

Я добавил дополнительное логическое значение, чтобы получать / получать уведомления не в первый раз после расчета макета.


Свойство Type предназначено для определения того, когда должны вызываться обратные вызовы.

  1. ON_SCROLL: для уведомления об обратном вызове, как только новый Просмотр / Страница проходит середину
  2. ON_SETTLED: для уведомления обратного вызова после того, как состояние RecyclerViews равно SCROLL_STATE_IDLE. Я использую режим только для вызова API, когда свиток исчерпан.
...