PagedSnapHelper.java revision f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2a
1b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen/* 2b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Copyright (C) 2017 The Android Open Source Project 3b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 4b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Licensed under the Apache License, Version 2.0 (the "License"); 5b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * you may not use this file except in compliance with the License. 6b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * You may obtain a copy of the License at 7b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 8b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * http://www.apache.org/licenses/LICENSE-2.0 9b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 10b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Unless required by applicable law or agreed to in writing, software 11b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * distributed under the License is distributed on an "AS IS" BASIS, 12b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * See the License for the specific language governing permissions and 14b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * limitations under the License. 15b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 16b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 17b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenpackage androidx.car.widget; 18b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 19f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chenimport android.content.Context; 20b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.support.annotation.NonNull; 21b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.support.annotation.Nullable; 22b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.support.v7.widget.LinearSnapHelper; 23b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.support.v7.widget.OrientationHelper; 24b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.support.v7.widget.RecyclerView; 25b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenimport android.view.View; 26b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 27b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen/** 28b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Extension of a {@link LinearSnapHelper} that will snap to the start of the target child view to 29b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * the start of the attached {@link RecyclerView}. The start of the view is defined as the top 30b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * if the RecyclerView is scrolling vertically; it is defined as the left (or right if RTL) if the 31b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * RecyclerView is scrolling horizontally. 32b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 33b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chenpublic class PagedSnapHelper extends LinearSnapHelper { 34b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** 35b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * The percentage of a View that needs to be completely visible for it to be a viable snap 36b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * target. 37b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 38b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private static final float VIEW_VISIBLE_THRESHOLD = 0.5f; 39b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 40f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen private final PagedSmoothScroller mSmoothScroller; 41b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private RecyclerView mRecyclerView; 42b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 43b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // Orientation helpers are lazily created per LayoutManager. 44b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Nullable 45b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private OrientationHelper mVerticalHelper; 46b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 47b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Nullable 48b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private OrientationHelper mHorizontalHelper; 49b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 50f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen public PagedSnapHelper(Context context) { 51f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen mSmoothScroller = new PagedSmoothScroller(context); 52f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen } 53f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen 54b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Override 55b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, 56b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @NonNull View targetView) { 57b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int[] out = new int[2]; 58b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 59b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen out[0] = layoutManager.canScrollHorizontally() 60b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? getHorizontalHelper(layoutManager).getDecoratedStart(targetView) 61b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen : 0; 62b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 63b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen out[1] = layoutManager.canScrollVertically() 64b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? getVerticalHelper(layoutManager).getDecoratedStart(targetView) 65b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen : 0; 66b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 67b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return out; 68b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 69b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 70b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** 71b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Finds the view to snap to. The view to snap to is the child of the LayoutManager that is 72b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * closest to the start of the RecyclerView. The "start" depends on if the LayoutManager 73b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * is scrolling horizontally or vertically. If it is horizontally scrolling, then the 74b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * start is the view on the left (right if RTL). Otherwise, it is the top-most view. 75b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 76b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param layoutManager The current {@link RecyclerView.LayoutManager} for the attached 77b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * RecyclerView. 78b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @return The View closest to the start of the RecyclerView. 79b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 80b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Override 81b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Nullable 82b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public View findSnapView(RecyclerView.LayoutManager layoutManager) { 83b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int childCount = layoutManager.getChildCount(); 84b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (childCount == 0) { 85b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return null; 86b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 87b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 88b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // If there's only one child, then that will be the snap target. 89b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (childCount == 1) { 90b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return layoutManager.getChildAt(0); 91b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 92b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 93b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen OrientationHelper orientationHelper = layoutManager.canScrollVertically() 94b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? getVerticalHelper(layoutManager) 95b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen : getHorizontalHelper(layoutManager); 96b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 97b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen View lastVisibleChild = layoutManager.getChildAt(childCount - 1); 98b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 99b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // Check if the last child visible is the last item in the list. 100b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen boolean lastItemVisible = 101b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen layoutManager.getPosition(lastVisibleChild) == layoutManager.getItemCount() - 1; 102b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 103b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // If it is, then check how much of that view is visible. 104b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen float lastItemPercentageVisible = lastItemVisible 105b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? getPercentageVisible(lastVisibleChild, orientationHelper) : 0; 106b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 107b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen View closestChild = null; 108b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int closestDistanceToStart = Integer.MAX_VALUE; 109b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen float closestPercentageVisible = 0.f; 110b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 111b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen for (int i = 0; i < childCount; i++) { 112b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen View child = layoutManager.getChildAt(i); 113b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int startOffset = orientationHelper.getDecoratedStart(child); 114b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 115b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (Math.abs(startOffset) < closestDistanceToStart) { 116b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen float percentageVisible = getPercentageVisible(child, orientationHelper); 117b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 118b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // Only snap to the child that is closest to the top and is more than 119b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // half-way visible. 120b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (percentageVisible > VIEW_VISIBLE_THRESHOLD 121b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen && percentageVisible > closestPercentageVisible) { 122b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen closestDistanceToStart = startOffset; 123b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen closestChild = child; 124b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen closestPercentageVisible = percentageVisible; 125b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 126b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 127b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 128b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 129b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // Snap to the last child in the list if it's the last item in the list, and it's more 130b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // visible than the closest item to the top of the list. 131b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return (lastItemVisible && lastItemPercentageVisible > closestPercentageVisible) 132b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? lastVisibleChild 133b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen : closestChild; 134b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 135b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 136b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** 137b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Returns the percentage of the given view that is visible, relative to its containing 138b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * RecyclerView. 139b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 140b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param view The View to get the percentage visible of. 141b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param helper An {@link OrientationHelper} to aid with calculation. 142b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @return A float indicating the percentage of the given view that is visible. 143b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 144b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private float getPercentageVisible(View view, OrientationHelper helper) { 145b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int start = 0; 146b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int end = helper.getEnd(); 147b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 148b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int viewStart = helper.getDecoratedStart(view); 149b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int viewEnd = helper.getDecoratedEnd(view); 150b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 151b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (viewStart >= start && viewEnd <= end) { 152b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The view is within the bounds of the RecyclerView, so it's fully visible. 153b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return 1.f; 154b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } else if (viewStart <= start && viewEnd >= end) { 155b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The view is larger than the height of the RecyclerView. 156b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int viewHeight = helper.getDecoratedMeasurement(view); 157b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return 1.f - ((float) (Math.abs(viewStart) + Math.abs(viewEnd)) / viewHeight); 158b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } else if (viewStart < start) { 159b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The view is above the start of the RecyclerView, so subtract the start offset 160b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // from the total height. 161b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return 1.f - ((float) Math.abs(viewStart) / helper.getDecoratedMeasurement(view)); 162b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } else { 163b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The view is below the end of the RecyclerView, so subtract the end offset from the 164b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // total height. 165b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return 1.f - ((float) Math.abs(viewEnd) / helper.getDecoratedMeasurement(view)); 166b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 167b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 168b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 169b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Override 170b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { 171b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen super.attachToRecyclerView(recyclerView); 172b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen mRecyclerView = recyclerView; 173b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 174b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 175b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** 176f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * Returns a scroller specific to this {@code PagedSnapHelper}. This scroller is used for all 177f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * smooth scrolling operations, including flings. 178f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * 179f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached 180f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * {@link RecyclerView}. 181f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * 182f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling. 183f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen */ 184f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen @Override 185f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) { 186f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen return mSmoothScroller; 187f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen } 188f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen 189f990b0b9c0d7851e15b5eb2f5014c2f6a010cb2aAnthony Chen /** 190b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Calculate the estimated scroll distance in each direction given velocities on both axes. 191b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * This method will clamp the maximum scroll distance so that a single fling will never scroll 192b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * more than one page. 193b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 194b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param velocityX Fling velocity on the horizontal axis. 195b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param velocityY Fling velocity on the vertical axis. 196b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @return An array holding the calculated distances in x and y directions respectively. 197b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 198b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @Override 199b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public int[] calculateScrollDistance(int velocityX, int velocityY) { 200b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int[] outDist = super.calculateScrollDistance(velocityX, velocityY); 201b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 202b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (mRecyclerView == null) { 203b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return outDist; 204b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 205b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 206b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 207b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (layoutManager == null || layoutManager.getChildCount() == 0) { 208b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return outDist; 209b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 210b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 211b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int lastChildPosition = isAtEnd(layoutManager) ? 0 : layoutManager.getChildCount() - 1; 212b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 213b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The max and min distance is the total height of the RecyclerView minus the height of 214b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // the last child. This ensures that each scroll will never scroll more than a single 215b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // page on the RecyclerView. That is, the max scroll will make the last child the 216b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // first child and vice versa when scrolling the opposite way. 217b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int maxDistance = layoutManager.getHeight() - layoutManager.getDecoratedMeasuredHeight( 218b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen layoutManager.getChildAt(lastChildPosition)); 219b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int minDistance = -maxDistance; 220b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 221b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen outDist[0] = clamp(outDist[0], minDistance, maxDistance); 222b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen outDist[1] = clamp(outDist[1], minDistance, maxDistance); 223b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 224b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return outDist; 225b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 226b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 227b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** Returns {@code true} if the RecyclerView is completely displaying the first item. */ 228b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public boolean isAtStart(RecyclerView.LayoutManager layoutManager) { 229b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (layoutManager == null || layoutManager.getChildCount() == 0) { 230b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return true; 231b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 232b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 233b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen View firstChild = layoutManager.getChildAt(0); 234b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen OrientationHelper orientationHelper = layoutManager.canScrollVertically() 235b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen ? getVerticalHelper(layoutManager) 236b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen : getHorizontalHelper(layoutManager); 237b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 238b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // Check that the first child is completely visible and is the first item in the list. 239b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return orientationHelper.getDecoratedStart(firstChild) >= 0 240b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen && layoutManager.getPosition(firstChild) == 0; 241b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 242b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 243b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** Returns {@code true} if the RecyclerView is completely displaying the last item. */ 244b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen public boolean isAtEnd(RecyclerView.LayoutManager layoutManager) { 245b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (layoutManager == null || layoutManager.getChildCount() == 0) { 246b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return true; 247b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 248b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 249b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen int childCount = layoutManager.getChildCount(); 250b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen View lastVisibleChild = layoutManager.getChildAt(childCount - 1); 251b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 252b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // The list has reached the bottom if the last child that is visible is the last item 253b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen // in the list and it's fully shown. 254b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return layoutManager.getPosition(lastVisibleChild) == (layoutManager.getItemCount() - 1) 255b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen && layoutManager.getDecoratedBottom(lastVisibleChild) <= layoutManager.getHeight(); 256b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 257b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 258b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @NonNull 259b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { 260b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) { 261b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); 262b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 263b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return mVerticalHelper; 264b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 265b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 266b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @NonNull 267b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private OrientationHelper getHorizontalHelper( 268b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen @NonNull RecyclerView.LayoutManager layoutManager) { 269b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen if (mHorizontalHelper == null || mHorizontalHelper.getLayoutManager() != layoutManager) { 270b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); 271b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 272b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return mHorizontalHelper; 273b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 274b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen 275b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen /** 276b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * Ensures that the given value falls between the range given by the min and max values. This 277b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * method does not check that the min value is greater than or equal to the max value. If the 278b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * parameters are not well-formed, this method's behavior is undefined. 279b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * 280b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param value The value to clamp. 281b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param min The minimum value the given value can be. 282b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @param max The maximum value the given value can be. 283b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * @return A number that falls between {@code min} or {@code max} or one of those values if the 284b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen * given value is less than or greater than {@code min} and {@code max} respectively. 285b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen */ 286b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen private static int clamp(int value, int min, int max) { 287b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen return Math.max(min, Math.min(max, value)); 288b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen } 289b226d4dcc396fe8837876dfa113143702dd23c26Anthony Chen} 290