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