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