Обновление
В исходном классе были некоторые проблемы с уведомлением о первом макете. Теперь он срабатывает только в первый раз, когда позиция предмета меняется с 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
предназначено для определения того, когда должны вызываться обратные вызовы.
- ON_SCROLL: для уведомления об обратном вызове, как только новый Просмотр / Страница проходит середину
- ON_SETTLED: для уведомления обратного вызова после того, как состояние RecyclerViews равно
SCROLL_STATE_IDLE
. Я использую режим только для вызова API, когда свиток исчерпан.