简体   繁体   中英

From-To Page Changed Listener in PagerSnapHelper

I'm using PagerSnapHelper in a horizontal RecyclerView to achieve a view pager like behaviour.

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

It works great, but I want to be able to get callbacks for when the user changes the page in either direction. So something like, onSwipeLeft / onSwipeRight callbacks.

I tried using findTargetSnapPosition in PagerSnapHelper , but that only gives me the targetIndex and not the current index. I tried something like this, but it doesn't really work all the time.

@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;
}

Is there a better way to achieve this which always works? Thanks!

Update 2
!! Important note !!
If you programmatically call scrollTo() and use the SnapPagerScrollListener with ON_SETTLED , onScrollStateChanged won't get called. So the old snap position does not get updated. WIP, will update the class as soon as I fix it.

Update

The original class had some issues with notifying on first layout. Now it fires only the first time the item position changes from RecyclerView.NO_POSITION to something else.

To further extend to ignore/fire only on user gestures, so non programmatic calls to scrollTo() , note that onScrolled() gets triggered with dx == 0 and dy == 0 in case of a programmatic call.

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;
    }
}


Usage:
Just add an instance of SnapPagerScrollListener to your RecyclerView

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


The Type property is for defining when the callbacks should be triggered.

  1. ON_SCROLL: for notifiying the callback as soon as as the new View/Page passes the middle
  2. ON_SETTLED: for notifying the callback after the RecyclerViews state is SCROLL_STATE_IDLE . I use the mode for only firing API calls when the scroll has settled.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM