FileDocCategorySizeDatePackage
LinearSmoothScroller.javaAPI DocAndroid 5.1 API14107Thu Mar 12 22:22:56 GMT 2015android.support.v7.widget

LinearSmoothScroller

public abstract class LinearSmoothScroller extends RecyclerView.SmoothScroller
{@link RecyclerView.SmoothScroller} implementation which uses {@link android.view.animation.LinearInterpolator} until the target position becames a child of the RecyclerView and then uses {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final float
MILLISECONDS_PER_INCH
private static final int
TARGET_SEEK_SCROLL_DISTANCE_PX
public static final int
SNAP_TO_START
Align child view's left or top with parent view's left or top
public static final int
SNAP_TO_END
Align child view's right or bottom with parent view's right or bottom
public static final int
SNAP_TO_ANY

Decides if the child should be snapped from start or end, depending on where it currently is in relation to its parent.

For instance, if the view is virtually on the left of RecyclerView, using {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}

private static final float
TARGET_SEEK_EXTRA_SCROLL_RATIO
protected final android.view.animation.LinearInterpolator
mLinearInterpolator
protected final android.view.animation.DecelerateInterpolator
mDecelerateInterpolator
protected android.graphics.PointF
mTargetVector
private final float
MILLISECONDS_PER_PX
protected int
mInterimTargetDx
protected int
mInterimTargetDy
Constructors Summary
public LinearSmoothScroller(android.content.Context context)


       
        MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
    
Methods Summary
public intcalculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference)
Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and {@link #calculateDyToMakeVisible(android.view.View, int)}

        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
                if (dtStart > 0) {
                    return dtStart;
                }
                final int dtEnd = boxEnd - viewEnd;
                if (dtEnd < 0) {
                    return dtEnd;
                }
                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
        return 0;
    
public intcalculateDxToMakeVisible(android.view.View view, int snapPreference)
Calculates the horizontal scroll amount necessary to make the given view fully visible inside the RecyclerView.

param
view The view which we want to make fully visible
param
snapPreference The edge which the view should snap to when entering the visible area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or {@link #SNAP_TO_END}
return
The vertical scroll amount necessary to make the view visible with the given snap preference.

        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
        if (!layoutManager.canScrollHorizontally()) {
            return 0;
        }
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
        final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
        final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
        final int start = layoutManager.getPaddingLeft();
        final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
        return calculateDtToFit(left, right, start, end, snapPreference);
    
public intcalculateDyToMakeVisible(android.view.View view, int snapPreference)
Calculates the vertical scroll amount necessary to make the given view fully visible inside the RecyclerView.

param
view The view which we want to make fully visible
param
snapPreference The edge which the view should snap to when entering the visible area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or {@link #SNAP_TO_END}.
return
The vertical scroll amount necessary to make the view visible with the given snap preference.

        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
        if (!layoutManager.canScrollVertically()) {
            return 0;
        }
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
        final int start = layoutManager.getPaddingTop();
        final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
        return calculateDtToFit(top, bottom, start, end, snapPreference);
    
protected floatcalculateSpeedPerPixel(android.util.DisplayMetrics displayMetrics)
Calculates the scroll speed.

param
displayMetrics DisplayMetrics to be used for real dimension calculations
return
The time (in ms) it should take for each pixel. For instance, if returned value is 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.

        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
    
protected intcalculateTimeForDeceleration(int dx)

Calculates the time for deceleration so that transition from LinearInterpolator to DecelerateInterpolator looks smooth.

param
dx Distance to scroll
return
Time for DecelerateInterpolator to smoothly traverse the distance when transitioning from LinearInterpolation

        // we want to cover same area with the linear interpolator for the first 10% of the
        // interpolation. After that, deceleration will take control.
        // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
        // which gives 0.100028 when x = .3356
        // this is why we divide linear scrolling time with .3356
        return  (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
    
protected intcalculateTimeForScrolling(int dx)
Calculates the time it should take to scroll the given distance (in pixels)

param
dx Distance in pixels that we want to scroll
return
Time in milliseconds
see
#calculateSpeedPerPixel(android.util.DisplayMetrics)

        // In a case where dx is very small, rounding may return 0 although dx > 0.
        // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
        // time.
        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
    
private intclampApplyScroll(int tmpDt, int dt)

        final int before = tmpDt;
        tmpDt -= dt;
        if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
            return 0;
        }
        return tmpDt;
    
public abstract android.graphics.PointFcomputeScrollVectorForPosition(int targetPosition)

protected intgetHorizontalSnapPreference()
When scrolling towards a child view, this method defines whether we should align the left or the right edge of the child with the parent RecyclerView.

return
SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
see
#SNAP_TO_START
see
#SNAP_TO_END
see
#SNAP_TO_ANY

        return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
                mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
    
protected intgetVerticalSnapPreference()
When scrolling towards a child view, this method defines whether we should align the top or the bottom edge of the child with the parent RecyclerView.

return
SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
see
#SNAP_TO_START
see
#SNAP_TO_END
see
#SNAP_TO_ANY

        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
    
protected voidonSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action)
{@inheritDoc}

        if (getChildCount() == 0) {
            stop();
            return;
        }
        if (DEBUG && mTargetVector != null
                && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
            throw new IllegalStateException("Scroll happened in the opposite direction"
                    + " of the target. Some calculations are wrong");
        }
        mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
        mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);

        if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
            updateActionForInterimTarget(action);
        } // everything is valid, keep going

    
protected voidonStart()
{@inheritDoc}


    
protected voidonStop()
{@inheritDoc}

        mInterimTargetDx = mInterimTargetDy = 0;
        mTargetVector = null;
    
protected voidonTargetFound(android.view.View targetView, RecyclerView.State state, Action action)
{@inheritDoc}

        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
        final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
        final int distance = (int) Math.sqrt(dx * dx + dy * dy);
        final int time = calculateTimeForDeceleration(distance);
        if (time > 0) {
            action.update(-dx, -dy, time, mDecelerateInterpolator);
        }
    
protected voidupdateActionForInterimTarget(Action action)
When the target scroll position is not a child of the RecyclerView, this method calculates a direction vector towards that child and triggers a smooth scroll.

see
#computeScrollVectorForPosition(int)

        // find an interim target position
        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
        if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
            Log.e(TAG, "To support smooth scrolling, you should override \n"
                    + "LayoutManager#computeScrollVectorForPosition.\n"
                    + "Falling back to instant scroll");
            final int target = getTargetPosition();
            stop();
            instantScrollToPosition(target);
            return;
        }
        normalize(scrollVector);
        mTargetVector = scrollVector;

        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
        // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
        // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
        // won't actually scroll more than what we need.
        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
                , (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
                , (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);