public abstract class

SnapHelper

extends RecyclerView.OnFlingListener

 java.lang.Object

androidx.recyclerview.widget.RecyclerView.OnFlingListener

↳androidx.recyclerview.widget.SnapHelper

Subclasses:

LinearSnapHelper, PagerSnapHelper

Gradle dependencies

compile group: 'androidx.recyclerview', name: 'recyclerview', version: '1.4.0-beta01'

  • groupId: androidx.recyclerview
  • artifactId: recyclerview
  • version: 1.4.0-beta01

Artifact androidx.recyclerview:recyclerview:1.4.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.recyclerview:recyclerview com.android.support:recyclerview-v7

Androidx class mapping:

androidx.recyclerview.widget.SnapHelper android.support.v7.widget.SnapHelper

Overview

Class intended to support snapping for a RecyclerView.

SnapHelper tries to handle fling as well but for this to work properly, the RecyclerView.LayoutManager must implement the RecyclerView.SmoothScroller.ScrollVectorProvider interface or you should override SnapHelper.onFling(int, int) and handle fling manually.

Summary

Constructors
publicSnapHelper()

Methods
public voidattachToRecyclerView(RecyclerView recyclerView)

Attaches the SnapHelper to the provided RecyclerView, by calling RecyclerView.setOnFlingListener(RecyclerView.OnFlingListener).

public abstract int[]calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView)

Override this method to snap to a particular point within the target view or the container view on any axis.

public int[]calculateScrollDistance(int velocityX, int velocityY)

Calculated the estimated scroll distance in each direction given velocities on both axes.

protected RecyclerView.SmoothScrollercreateScroller(RecyclerView.LayoutManager layoutManager)

Creates a scroller to be used in the snapping implementation.

protected LinearSmoothScrollercreateSnapScroller(RecyclerView.LayoutManager layoutManager)

Creates a scroller to be used in the snapping implementation.

public abstract ViewfindSnapView(RecyclerView.LayoutManager layoutManager)

Override this method to provide a particular target view for snapping.

public abstract intfindTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY)

Override to provide a particular adapter target position for snapping.

public abstract booleanonFling(int velocityX, int velocityY)

Override this to handle a fling given the velocities in both x and y directions.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SnapHelper()

Methods

public abstract boolean onFling(int velocityX, int velocityY)

Override this to handle a fling given the velocities in both x and y directions. Note that this method will only be called if the associated RecyclerView.LayoutManager supports scrolling and the fling is not handled by nested scrolls first.

Parameters:

velocityX: the fling velocity on the X axis
velocityY: the fling velocity on the Y axis

Returns:

true if the fling was handled, false otherwise.

public void attachToRecyclerView(RecyclerView recyclerView)

Attaches the SnapHelper to the provided RecyclerView, by calling RecyclerView.setOnFlingListener(RecyclerView.OnFlingListener). You can call this method with null to detach it from the current RecyclerView.

Parameters:

recyclerView: The RecyclerView instance to which you want to add this helper or null if you want to remove SnapHelper from the current RecyclerView.

public int[] calculateScrollDistance(int velocityX, int velocityY)

Calculated the estimated scroll distance in each direction given velocities on both axes.

Parameters:

velocityX: Fling velocity on the horizontal axis.
velocityY: Fling velocity on the vertical axis.

Returns:

array holding the calculated distances in x and y directions respectively.

protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager)

Creates a scroller to be used in the snapping implementation.

Parameters:

layoutManager: The RecyclerView.LayoutManager associated with the attached RecyclerView.

Returns:

a RecyclerView.SmoothScroller which will handle the scrolling.

protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager)

Deprecated: use SnapHelper.createScroller(RecyclerView.LayoutManager) instead.

Creates a scroller to be used in the snapping implementation.

Parameters:

layoutManager: The RecyclerView.LayoutManager associated with the attached RecyclerView.

Returns:

a LinearSmoothScroller which will handle the scrolling.

public abstract int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView)

Override this method to snap to a particular point within the target view or the container view on any axis.

This method is called when the SnapHelper has intercepted a fling and it needs to know the exact distance required to scroll by in order to snap to the target view.

Parameters:

layoutManager: the RecyclerView.LayoutManager associated with the attached RecyclerView
targetView: the target view that is chosen as the view to snap

Returns:

the output coordinates the put the result into. out[0] is the distance on horizontal axis and out[1] is the distance on vertical axis.

public abstract View findSnapView(RecyclerView.LayoutManager layoutManager)

Override this method to provide a particular target view for snapping.

This method is called when the SnapHelper is ready to start snapping and requires a target view to snap to. It will be explicitly called when the scroll state becomes idle after a scroll. It will also be called when the SnapHelper is preparing to snap after a fling and requires a reference view from the current set of child views.

If this method returns null, SnapHelper will not snap to any view.

Parameters:

layoutManager: the RecyclerView.LayoutManager associated with the attached RecyclerView

Returns:

the target view to which to snap on fling or end of scroll

public abstract int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY)

Override to provide a particular adapter target position for snapping.

Parameters:

layoutManager: the RecyclerView.LayoutManager associated with the attached RecyclerView
velocityX: fling velocity on the horizontal axis
velocityY: fling velocity on the vertical axis

Returns:

the target adapter position to you want to snap or RecyclerView.NO_POSITION if no snapping should happen

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.recyclerview.widget;

import android.annotation.SuppressLint;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Class intended to support snapping for a {@link RecyclerView}.
 * <p>
 * SnapHelper tries to handle fling as well but for this to work properly, the
 * {@link RecyclerView.LayoutManager} must implement the {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface or
 * you should override {@link #onFling(int, int)} and handle fling manually.
 */
public abstract class SnapHelper extends RecyclerView.OnFlingListener {

    static final float MILLISECONDS_PER_INCH = 100f;

    RecyclerView mRecyclerView;
    private Scroller mGravityScroller;

    // Handles the snap on scroll case.
    private final RecyclerView.OnScrollListener mScrollListener =
            new RecyclerView.OnScrollListener() {
                boolean mScrolled = false;

                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
                        mScrolled = false;
                        snapToTargetExistingView();
                    }
                }

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    if (dx != 0 || dy != 0) {
                        mScrolled = true;
                    }
                }
            };

    @Override
    public boolean onFling(int velocityX, int velocityY) {
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager == null) {
            return false;
        }
        RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
        if (adapter == null) {
            return false;
        }
        int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
        return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
                && snapFromFling(layoutManager, velocityX, velocityY);
    }

    /**
     * Attaches the {@link SnapHelper} to the provided RecyclerView, by calling
     * {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}.
     * You can call this method with {@code null} to detach it from the current RecyclerView.
     *
     * @param recyclerView The RecyclerView instance to which you want to add this helper or
     *                     {@code null} if you want to remove SnapHelper from the current
     *                     RecyclerView.
     *
     * @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener}
     * attached to the provided {@link RecyclerView}.
     *
     */
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
            throws IllegalStateException {
        if (mRecyclerView == recyclerView) {
            return; // nothing to do
        }
        if (mRecyclerView != null) {
            destroyCallbacks();
        }
        mRecyclerView = recyclerView;
        if (mRecyclerView != null) {
            setupCallbacks();
            mGravityScroller = new Scroller(mRecyclerView.getContext(),
                    new DecelerateInterpolator());
            snapToTargetExistingView();
        }
    }

    /**
     * Called when an instance of a {@link RecyclerView} is attached.
     */
    private void setupCallbacks() throws IllegalStateException {
        if (mRecyclerView.getOnFlingListener() != null) {
            throw new IllegalStateException("An instance of OnFlingListener already set.");
        }
        mRecyclerView.addOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(this);
    }

    /**
     * Called when the instance of a {@link RecyclerView} is detached.
     */
    private void destroyCallbacks() {
        mRecyclerView.removeOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(null);
    }

    /**
     * Calculated the estimated scroll distance in each direction given velocities on both axes.
     *
     * @param velocityX     Fling velocity on the horizontal axis.
     * @param velocityY     Fling velocity on the vertical axis.
     *
     * @return array holding the calculated distances in x and y directions
     * respectively.
     */
    @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
    public int[] calculateScrollDistance(int velocityX, int velocityY) {
        int[] outDist = new int[2];
        mGravityScroller.fling(0, 0, velocityX, velocityY,
                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
        outDist[0] = mGravityScroller.getFinalX();
        outDist[1] = mGravityScroller.getFinalY();
        return outDist;
    }

    /**
     * Helper method to facilitate for snapping triggered by a fling.
     *
     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}.
     * @param velocityX     Fling velocity on the horizontal axis.
     * @param velocityY     Fling velocity on the vertical axis.
     *
     * @return true if it is handled, false otherwise.
     */
    private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return false;
        }

        RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
        if (smoothScroller == null) {
            return false;
        }

        int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
        if (targetPosition == RecyclerView.NO_POSITION) {
            return false;
        }

        smoothScroller.setTargetPosition(targetPosition);
        layoutManager.startSmoothScroll(smoothScroller);
        return true;
    }

    /**
     * Snaps to a target view which currently exists in the attached {@link RecyclerView}. This
     * method is used to snap the view when the {@link RecyclerView} is first attached; when
     * snapping was triggered by a scroll and when the fling is at its final stages.
     */
    void snapToTargetExistingView() {
        if (mRecyclerView == null) {
            return;
        }
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager == null) {
            return;
        }
        View snapView = findSnapView(layoutManager);
        if (snapView == null) {
            return;
        }
        int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
        if (snapDistance[0] != 0 || snapDistance[1] != 0) {
            mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
        }
    }

    /**
     * Creates a scroller to be used in the snapping implementation.
     *
     * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
     *                          {@link RecyclerView}.
     *
     * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling.
     */
    @Nullable
    protected RecyclerView.SmoothScroller createScroller(
            @NonNull RecyclerView.LayoutManager layoutManager) {
        return createSnapScroller(layoutManager);
    }

    /**
     * Creates a scroller to be used in the snapping implementation.
     *
     * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
     *                          {@link RecyclerView}.
     *
     * @return a {@link LinearSmoothScroller} which will handle the scrolling.
     * @deprecated use {@link #createScroller(RecyclerView.LayoutManager)} instead.
     */
    @Nullable
    @Deprecated
    protected LinearSmoothScroller createSnapScroller(
            @NonNull RecyclerView.LayoutManager layoutManager) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return null;
        }
        return new LinearSmoothScroller(mRecyclerView.getContext()) {
            @Override
            protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                if (mRecyclerView == null) {
                    // The associated RecyclerView has been removed so there is no action to take.
                    return;
                }
                int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                        targetView);
                final int dx = snapDistances[0];
                final int dy = snapDistances[1];
                final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                if (time > 0) {
                    action.update(dx, dy, time, mDecelerateInterpolator);
                }
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };
    }

    /**
     * Override this method to snap to a particular point within the target view or the container
     * view on any axis.
     * <p>
     * This method is called when the {@link SnapHelper} has intercepted a fling and it needs
     * to know the exact distance required to scroll by in order to snap to the target view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param targetView the target view that is chosen as the view to snap
     *
     * @return the output coordinates the put the result into. out[0] is the distance
     * on horizontal axis and out[1] is the distance on vertical axis.
     */
    @SuppressWarnings("WeakerAccess")
    @Nullable
    public abstract int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView);

    /**
     * Override this method to provide a particular target view for snapping.
     * <p>
     * This method is called when the {@link SnapHelper} is ready to start snapping and requires
     * a target view to snap to. It will be explicitly called when the scroll state becomes idle
     * after a scroll. It will also be called when the {@link SnapHelper} is preparing to snap
     * after a fling and requires a reference view from the current set of child views.
     * <p>
     * If this method returns {@code null}, SnapHelper will not snap to any view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     *
     * @return the target view to which to snap on fling or end of scroll
     */
    @SuppressWarnings("WeakerAccess")
    @Nullable
    @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
    public abstract View findSnapView(RecyclerView.LayoutManager layoutManager);

    /**
     * Override to provide a particular adapter target position for snapping.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param velocityX fling velocity on the horizontal axis
     * @param velocityY fling velocity on the vertical axis
     *
     * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION}
     *         if no snapping should happen
     */
    @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
    public abstract int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
            int velocityX, int velocityY);
}