10447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar/*
20447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * Copyright (C) 2014 The Android Open Source Project
30447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar *
40447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
50447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * you may not use this file except in compliance with the License.
60447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * You may obtain a copy of the License at
70447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar *
80447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
90447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar *
100447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * Unless required by applicable law or agreed to in writing, software
110447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
120447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * See the License for the specific language governing permissions and
140447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * limitations under the License.
150447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar */
160447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
170447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarpackage android.support.v7.widget;
180447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
190447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.content.Context;
200447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.graphics.PointF;
210447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.util.DisplayMetrics;
220447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.util.Log;
230447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.view.View;
240447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.view.animation.DecelerateInterpolator;
250447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarimport android.view.animation.LinearInterpolator;
260447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
270447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar/**
280447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * {@link RecyclerView.SmoothScroller} implementation which uses
290447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * {@link android.view.animation.LinearInterpolator} until the target position becames a child of
300447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * the RecyclerView and then uses
310447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar * {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position.
320447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar */
330447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyarabstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
340447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
350447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private static final String TAG = "LinearSmoothScroller";
360447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
370447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private static final boolean DEBUG = false;
380447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
390447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private static final float MILLISECONDS_PER_INCH = 25f;
400447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
410447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
420447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
430447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
440447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Align child view's left or top with parent view's left or top
450447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
460447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDtToFit(int, int, int, int, int)
470447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDxToMakeVisible(android.view.View, int)
480447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDyToMakeVisible(android.view.View, int)
490447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
500447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public static final int SNAP_TO_START = -1;
510447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
520447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
530447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Align child view's right or bottom with parent view's right or bottom
540447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
550447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDtToFit(int, int, int, int, int)
560447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDxToMakeVisible(android.view.View, int)
570447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDyToMakeVisible(android.view.View, int)
580447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
590447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public static final int SNAP_TO_END = 1;
600447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
610447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
620447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * <p>Decides if the child should be snapped from start or end, depending on where it
630447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * currently is in relation to its parent.</p>
640447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * <p>For instance, if the view is virtually on the left of RecyclerView, using
650447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
660447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
670447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDtToFit(int, int, int, int, int)
680447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDxToMakeVisible(android.view.View, int)
690447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateDyToMakeVisible(android.view.View, int)
700447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
710447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public static final int SNAP_TO_ANY = 0;
720447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
730447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
740447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    // view is not laid out until interim target position is reached, we can detect the case before
750447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    // scrolling slows down and reschedule another interim target scroll
760447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
770447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
780447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
790447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
800447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
810447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
820447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected PointF mTargetVector;
830447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
840447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private final float MILLISECONDS_PER_PX;
850447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
860447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    // Temporary variables to keep track of the interim scroll target. These values do not
870447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    // point to a real item position, rather point to an estimated location pixels.
880447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
890447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
900447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public LinearSmoothScroller(Context context) {
910447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
920447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
930447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
940447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
950447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@inheritDoc}
960447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
970447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    @Override
980447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected void onStart() {
990447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1000447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1010447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1020447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1030447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@inheritDoc}
1040447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1050447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    @Override
1060447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
1070447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
1080447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
1090447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int distance = (int) Math.sqrt(dx * dx + dy * dy);
1100447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int time = calculateTimeForDeceleration(distance);
11120d3ab4bc623635334429240be31f13837e675cdYigit Boyar        if (time > 0) {
11220d3ab4bc623635334429240be31f13837e675cdYigit Boyar            action.update(-dx, -dy, time, mDecelerateInterpolator);
11320d3ab4bc623635334429240be31f13837e675cdYigit Boyar        }
1140447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1150447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1160447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1170447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@inheritDoc}
1180447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1190447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    @Override
1200447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
1210447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (getChildCount() == 0) {
1220447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            stop();
1236598bbee8b4e7af155671a5b40039c00b347954fYigit Boyar            return;
1240447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
1250447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (DEBUG && mTargetVector != null
1260447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
1270447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            throw new IllegalStateException("Scroll happened in the opposite direction"
1280447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                    + " of the target. Some calculations are wrong");
1290447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
1300447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
1310447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
1320447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1330447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
1340447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            updateActionForInterimTarget(action);
1350447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        } // everything is valid, keep going
1360447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1370447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1380447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1390447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1400447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@inheritDoc}
1410447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1420447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    @Override
1430447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected void onStop() {
1440447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mInterimTargetDx = mInterimTargetDy = 0;
1450447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mTargetVector = null;
1460447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1470447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1480447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1490447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Calculates the scroll speed.
1500447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
1510447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param displayMetrics DisplayMetrics to be used for real dimension calculations
1520447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return The time (in ms) it should take for each pixel. For instance, if returned value is
1530447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
1540447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1550447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
1560447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
1570447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1580447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1590447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1600447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * <p>Calculates the time for deceleration so that transition from LinearInterpolator to
1610447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * DecelerateInterpolator looks smooth.</p>
1620447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
1630447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param dx Distance to scroll
1640447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
1650447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * from LinearInterpolation
1660447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1670447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected int calculateTimeForDeceleration(int dx) {
1680447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // we want to cover same area with the linear interpolator for the first 10% of the
1690447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // interpolation. After that, deceleration will take control.
1700447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
1710447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // which gives 0.100028 when x = .3356
1720447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // this is why we divide linear scrolling time with .3356
17340ec11724622a83b36ce52bd4c474817c0c224adYigit Boyar        return  (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
1740447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1750447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1760447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1770447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Calculates the time it should take to scroll the given distance (in pixels)
1780447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
1790447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param dx Distance in pixels that we want to scroll
1800447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return Time in milliseconds
1810447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
1820447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1830447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected int calculateTimeForScrolling(int dx) {
18440ec11724622a83b36ce52bd4c474817c0c224adYigit Boyar        // In a case where dx is very small, rounding may return 0 although dx > 0.
18540ec11724622a83b36ce52bd4c474817c0c224adYigit Boyar        // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
18640ec11724622a83b36ce52bd4c474817c0c224adYigit Boyar        // time.
18740ec11724622a83b36ce52bd4c474817c0c224adYigit Boyar        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
1880447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
1890447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
1900447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
1910447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * When scrolling towards a child view, this method defines whether we should align the left
1920447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * or the right edge of the child with the parent RecyclerView.
1930447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
1940447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
1950447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_START
1960447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_END
1970447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_ANY
1980447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
1990447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected int getHorizontalSnapPreference() {
2000447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
2010447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
2020447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
2030447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2040447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
2050447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * When scrolling towards a child view, this method defines whether we should align the top
2060447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * or the bottom edge of the child with the parent RecyclerView.
2070447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
2080447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
2090447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_START
2100447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_END
2110447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #SNAP_TO_ANY
2120447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
2130447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected int getVerticalSnapPreference() {
2140447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
2150447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
2160447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
2170447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2180447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
2190447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * When the target scroll position is not a child of the RecyclerView, this method calculates
2200447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * a direction vector towards that child and triggers a smooth scroll.
2210447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
2220447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @see #computeScrollVectorForPosition(int)
2230447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
2240447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    protected void updateActionForInterimTarget(Action action) {
2250447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // find an interim target position
2260447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
2270447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
2280447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            Log.e(TAG, "To support smooth scrolling, you should override \n"
2290447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                    + "LayoutManager#computeScrollVectorForPosition.\n"
2300447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                    + "Falling back to instant scroll");
2310447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            final int target = getTargetPosition();
2320447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            stop();
2330447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            instantScrollToPosition(target);
2340447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            return;
2350447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
2360447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        normalize(scrollVector);
2370447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mTargetVector = scrollVector;
2380447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2390447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
2400447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
2410447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
2420447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
2430447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
2440447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        // won't actually scroll more than what we need.
2450447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
2460447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                , (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
2470447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                , (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
2480447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
2490447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2500447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    private int clampApplyScroll(int tmpDt, int dt) {
2510447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int before = tmpDt;
2520447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        tmpDt -= dt;
2530447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
2540447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            return 0;
2550447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
2560447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return tmpDt;
2570447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
2580447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2590447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
2600447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
2610447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * {@link #calculateDyToMakeVisible(android.view.View, int)}
2620447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
2630447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
2640447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            snapPreference) {
2650447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        switch (snapPreference) {
2660447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            case SNAP_TO_START:
2670447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                return boxStart - viewStart;
2680447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            case SNAP_TO_END:
2690447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                return boxEnd - viewEnd;
2700447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            case SNAP_TO_ANY:
2710447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                final int dtStart = boxStart - viewStart;
2720447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                if (dtStart > 0) {
2730447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                    return dtStart;
2740447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                }
2750447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                final int dtEnd = boxEnd - viewEnd;
2760447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                if (dtEnd < 0) {
2770447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                    return dtEnd;
2780447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                }
2790447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                break;
2800447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            default:
2810447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                throw new IllegalArgumentException("snap preference should be one of the"
2820447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                        + " constants defined in SmoothScroller, starting with SNAP_");
2830447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
2840447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return 0;
2850447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
2860447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
2870447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
2880447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Calculates the vertical scroll amount necessary to make the given view fully visible
2890447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * inside the RecyclerView.
2900447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
2910447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param view           The view which we want to make fully visible
2920447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param snapPreference The edge which the view should snap to when entering the visible
2930447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
2940447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *                       {@link #SNAP_TO_END}.
2950447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return The vertical scroll amount necessary to make the view visible with the given
2960447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * snap preference.
2970447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
2980447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public int calculateDyToMakeVisible(View view, int snapPreference) {
2990447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
3000447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (!layoutManager.canScrollVertically()) {
3010447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            return 0;
3020447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
3030447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
3040447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                view.getLayoutParams();
3050447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
3060447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
3070447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int start = layoutManager.getPaddingTop();
3080447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
3090447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return calculateDtToFit(top, bottom, start, end, snapPreference);
3100447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
3110447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
3120447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    /**
3130447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * Calculates the horizontal scroll amount necessary to make the given view fully visible
3140447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * inside the RecyclerView.
3150447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *
3160447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param view           The view which we want to make fully visible
3170447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @param snapPreference The edge which the view should snap to when entering the visible
3180447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
3190447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     *                       {@link #SNAP_TO_END}
3200447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * @return The vertical scroll amount necessary to make the view visible with the given
3210447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     * snap preference.
3220447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar     */
3230447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    public int calculateDxToMakeVisible(View view, int snapPreference) {
3240447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
3250447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        if (!layoutManager.canScrollHorizontally()) {
3260447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar            return 0;
3270447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        }
3280447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
3290447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar                view.getLayoutParams();
3300447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
3310447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
3320447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int start = layoutManager.getPaddingLeft();
3330447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
3340447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar        return calculateDtToFit(left, right, start, end, snapPreference);
3350447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    }
3360447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar
3370447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar    abstract public PointF computeScrollVectorForPosition(int targetPosition);
3380447ba889146f60d6965e6ea66fa4e2cac4d0891Yigit Boyar}
339