1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.recyclerview.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20 
21 import android.annotation.SuppressLint;
22 import android.content.Context;
23 import android.graphics.PointF;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.os.Trace;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.accessibility.AccessibilityEvent;
34 
35 import androidx.annotation.RestrictTo;
36 import androidx.core.view.ViewCompat;
37 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
38 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
39 
40 import org.jspecify.annotations.NonNull;
41 import org.jspecify.annotations.Nullable;
42 
43 import java.util.List;
44 
45 /**
46  * A {@link RecyclerView.LayoutManager} implementation which provides
47  * similar functionality to {@link android.widget.ListView}.
48  */
49 public class LinearLayoutManager extends RecyclerView.LayoutManager implements
50         ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
51 
52     private static final String TAG = "LinearLayoutManager";
53 
54     static final boolean DEBUG = false;
55 
56     public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
57 
58     public static final int VERTICAL = RecyclerView.VERTICAL;
59 
60     public static final int INVALID_OFFSET = Integer.MIN_VALUE;
61 
62 
63     /**
64      * While trying to find next view to focus, LayoutManager will not try to scroll more
65      * than this factor times the total space of the list. If layout is vertical, total space is the
66      * height minus padding, if layout is horizontal, total space is the width minus padding.
67      */
68     private static final float MAX_SCROLL_FACTOR = 1 / 3f;
69 
70     /**
71      * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
72      */
73     @RecyclerView.Orientation
74     int mOrientation = RecyclerView.DEFAULT_ORIENTATION;
75 
76     /**
77      * Helper class that keeps temporary layout state.
78      * It does not keep state after layout is complete but we still keep a reference to re-use
79      * the same object.
80      */
81     private LayoutState mLayoutState;
82 
83     /**
84      * Many calculations are made depending on orientation. To keep it clean, this interface
85      * helps {@link LinearLayoutManager} make those decisions.
86      */
87     OrientationHelper mOrientationHelper;
88 
89     /**
90      * We need to track this so that we can ignore current position when it changes.
91      */
92     private boolean mLastStackFromEnd;
93 
94 
95     /**
96      * Defines if layout should be calculated from end to start.
97      *
98      * @see #mShouldReverseLayout
99      */
100     private boolean mReverseLayout = false;
101 
102     /**
103      * This keeps the final value for how LayoutManager should start laying out views.
104      * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
105      * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
106      */
107     boolean mShouldReverseLayout = false;
108 
109     /**
110      * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
111      * it supports both orientations.
112      * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
113      */
114     private boolean mStackFromEnd = false;
115 
116     /**
117      * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
118      * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
119      */
120     private boolean mSmoothScrollbarEnabled = true;
121 
122     /**
123      * When LayoutManager needs to scroll to a position, it sets this variable and requests a
124      * layout which will check this variable and re-layout accordingly.
125      */
126     int mPendingScrollPosition = RecyclerView.NO_POSITION;
127 
128     /**
129      * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
130      * called.
131      */
132     int mPendingScrollPositionOffset = INVALID_OFFSET;
133 
134     private boolean mRecycleChildrenOnDetach;
135 
136     SavedState mPendingSavedState = null;
137 
138     /**
139      * Re-used variable to keep anchor information on re-layout.
140      * Anchor position and coordinate defines the reference point for LLM while doing a layout.
141      */
142     final AnchorInfo mAnchorInfo = new AnchorInfo();
143 
144     /**
145      * Stashed to avoid allocation, currently only used in #fill()
146      */
147     private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
148 
149     /**
150      * Number of items to prefetch when first coming on screen with new data.
151      */
152     private int mInitialPrefetchItemCount = 2;
153 
154     // Reusable int array to be passed to method calls that mutate it in order to "return" two ints.
155     // This should only be used used transiently and should not be used to retain any state over
156     // time.
157     private int[] mReusableIntPair = new int[2];
158 
159     /**
160      * Creates a vertical LinearLayoutManager
161      *
162      * @param context Current context, will be used to access resources.
163      */
LinearLayoutManager( @uppressLint"UnknownNullness") Context context )164     public LinearLayoutManager(
165             // Suppressed because fixing it requires a source-incompatible change to a very
166             // commonly used constructor, for no benefit: the context parameter is unused
167             @SuppressLint("UnknownNullness") Context context
168     ) {
169         this(context, RecyclerView.DEFAULT_ORIENTATION, false);
170     }
171 
172     /**
173      * @param context       Current context, will be used to access resources.
174      * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link
175      *                      #VERTICAL}.
176      * @param reverseLayout When set to true, layouts from end to start.
177      */
LinearLayoutManager( @uppressLint"UnknownNullness") Context context, @RecyclerView.Orientation int orientation, boolean reverseLayout )178     public LinearLayoutManager(
179             // Suppressed because fixing it requires a source-incompatible change to a very
180             // commonly used constructor, for no benefit: the context parameter is unused
181             @SuppressLint("UnknownNullness") Context context,
182             @RecyclerView.Orientation int orientation,
183             boolean reverseLayout
184     ) {
185         setOrientation(orientation);
186         setReverseLayout(reverseLayout);
187     }
188 
189     /**
190      * Constructor used when layout manager is set in XML by RecyclerView attribute
191      * "layoutManager". Defaults to vertical orientation.
192      *
193      * {@link android.R.attr#orientation}
194      * {@link androidx.recyclerview.R.attr#reverseLayout}
195      * {@link androidx.recyclerview.R.attr#stackFromEnd}
196      */
197     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)198     public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
199             int defStyleRes) {
200         Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
201         setOrientation(properties.orientation);
202         setReverseLayout(properties.reverseLayout);
203         setStackFromEnd(properties.stackFromEnd);
204     }
205 
206     @Override
isAutoMeasureEnabled()207     public boolean isAutoMeasureEnabled() {
208         return true;
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     @Override
215     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
generateDefaultLayoutParams()216     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
217         return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
218                 ViewGroup.LayoutParams.WRAP_CONTENT);
219     }
220 
221     /**
222      * Returns whether LayoutManager will recycle its children when it is detached from
223      * RecyclerView.
224      *
225      * @return true if LayoutManager will recycle its children when it is detached from
226      * RecyclerView.
227      */
getRecycleChildrenOnDetach()228     public boolean getRecycleChildrenOnDetach() {
229         return mRecycleChildrenOnDetach;
230     }
231 
232     /**
233      * Set whether LayoutManager will recycle its children when it is detached from
234      * RecyclerView.
235      * <p>
236      * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
237      * this flag to <code>true</code> so that views will be available to other RecyclerViews
238      * immediately.
239      * <p>
240      * Note that, setting this flag will result in a performance drop if RecyclerView
241      * is restored.
242      *
243      * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
244      */
setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach)245     public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
246         mRecycleChildrenOnDetach = recycleChildrenOnDetach;
247     }
248 
249     @Override
250     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler)251     public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
252         super.onDetachedFromWindow(view, recycler);
253         if (mRecycleChildrenOnDetach) {
254             removeAndRecycleAllViews(recycler);
255             recycler.clear();
256         }
257     }
258 
259     @Override
260     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onInitializeAccessibilityEvent(AccessibilityEvent event)261     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
262         super.onInitializeAccessibilityEvent(event);
263         if (getChildCount() > 0) {
264             event.setFromIndex(findFirstVisibleItemPosition());
265             event.setToIndex(findLastVisibleItemPosition());
266         }
267     }
268 
269     @Override
onInitializeAccessibilityNodeInfo(RecyclerView.@onNull Recycler recycler, RecyclerView.@NonNull State state, @NonNull AccessibilityNodeInfoCompat info)270     public void onInitializeAccessibilityNodeInfo(RecyclerView.@NonNull Recycler recycler,
271             RecyclerView.@NonNull State state, @NonNull AccessibilityNodeInfoCompat info) {
272         super.onInitializeAccessibilityNodeInfo(recycler, state, info);
273         // TODO(b/251823537)
274         if (mRecyclerView.mAdapter != null && mRecyclerView.mAdapter.getItemCount() > 0) {
275             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
276                 info.addAction(AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION);
277             }
278         }
279     }
280 
281     @Override
performAccessibilityAction(int action, @Nullable Bundle args)282     boolean performAccessibilityAction(int action, @Nullable Bundle args) {
283         if (super.performAccessibilityAction(action, args)) {
284             return true;
285         }
286 
287         if (action == android.R.id.accessibilityActionScrollToPosition && args != null) {
288             int position = -1;
289 
290             if (mOrientation == VERTICAL) {
291                 final int rowArg = args.getInt(
292                         AccessibilityNodeInfoCompat.ACTION_ARGUMENT_ROW_INT, -1);
293                 if (rowArg < 0) {
294                     return false;
295                 }
296                 position = Math.min(rowArg, getRowCountForAccessibility(mRecyclerView.mRecycler,
297                         mRecyclerView.mState) - 1);
298             } else { // horizontal
299                 final int columnArg = args.getInt(
300                         AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT, -1);
301                 if (columnArg < 0) {
302                     return false;
303                 }
304                 position = Math.min(columnArg,
305                         getColumnCountForAccessibility(mRecyclerView.mRecycler,
306                                 mRecyclerView.mState) - 1);
307             }
308             if (position >= 0) {
309                 // We want the target element to be the first on screen. That way, a
310                 // screenreader like Talkback can directly focus on it as part of its default focus
311                 // logic.
312                 scrollToPositionWithOffset(position, 0);
313                 return true;
314             }
315         }
316         return false;
317     }
318 
319     @Override
320     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onSaveInstanceState()321     public Parcelable onSaveInstanceState() {
322         if (mPendingSavedState != null) {
323             return new SavedState(mPendingSavedState);
324         }
325         SavedState state = new SavedState();
326         if (getChildCount() > 0) {
327             ensureLayoutState();
328             boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
329             state.mAnchorLayoutFromEnd = didLayoutFromEnd;
330             if (didLayoutFromEnd) {
331                 final View refChild = getChildClosestToEnd();
332                 state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
333                         - mOrientationHelper.getDecoratedEnd(refChild);
334                 state.mAnchorPosition = getPosition(refChild);
335             } else {
336                 final View refChild = getChildClosestToStart();
337                 state.mAnchorPosition = getPosition(refChild);
338                 state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
339                         - mOrientationHelper.getStartAfterPadding();
340             }
341         } else {
342             state.invalidateAnchor();
343         }
344         return state;
345     }
346 
347     @Override
348     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onRestoreInstanceState(Parcelable state)349     public void onRestoreInstanceState(Parcelable state) {
350         if (state instanceof SavedState) {
351             mPendingSavedState = (SavedState) state;
352             if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
353                 mPendingSavedState.invalidateAnchor();
354             }
355             requestLayout();
356             if (DEBUG) {
357                 Log.d(TAG, "loaded saved state");
358             }
359         } else if (DEBUG) {
360             Log.d(TAG, "invalid saved state class");
361         }
362     }
363 
364     /**
365      * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
366      */
367     @Override
canScrollHorizontally()368     public boolean canScrollHorizontally() {
369         return mOrientation == HORIZONTAL;
370     }
371 
372     /**
373      * @return true if {@link #getOrientation()} is {@link #VERTICAL}
374      */
375     @Override
canScrollVertically()376     public boolean canScrollVertically() {
377         return mOrientation == VERTICAL;
378     }
379 
380     @Override
isLayoutReversed()381     public boolean isLayoutReversed() {
382         return mReverseLayout;
383     }
384 
385     /**
386      * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
387      */
setStackFromEnd(boolean stackFromEnd)388     public void setStackFromEnd(boolean stackFromEnd) {
389         assertNotInLayoutOrScroll(null);
390         if (mStackFromEnd == stackFromEnd) {
391             return;
392         }
393         mStackFromEnd = stackFromEnd;
394         requestLayout();
395     }
396 
getStackFromEnd()397     public boolean getStackFromEnd() {
398         return mStackFromEnd;
399     }
400 
401     /**
402      * Returns the current orientation of the layout.
403      *
404      * @return Current orientation,  either {@link #HORIZONTAL} or {@link #VERTICAL}
405      * @see #setOrientation(int)
406      */
407     @RecyclerView.Orientation
getOrientation()408     public int getOrientation() {
409         return mOrientation;
410     }
411 
412     /**
413      * Sets the orientation of the layout. {@link LinearLayoutManager}
414      * will do its best to keep scroll position.
415      *
416      * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
417      */
setOrientation(@ecyclerView.Orientation int orientation)418     public void setOrientation(@RecyclerView.Orientation int orientation) {
419         if (orientation != HORIZONTAL && orientation != VERTICAL) {
420             throw new IllegalArgumentException("invalid orientation:" + orientation);
421         }
422 
423         assertNotInLayoutOrScroll(null);
424 
425         if (orientation != mOrientation || mOrientationHelper == null) {
426             mOrientationHelper =
427                     OrientationHelper.createOrientationHelper(this, orientation);
428             mAnchorInfo.mOrientationHelper = mOrientationHelper;
429             mOrientation = orientation;
430             requestLayout();
431         }
432     }
433 
434     /**
435      * Calculates the view layout order. (e.g. from end to start or start to end)
436      * RTL layout support is applied automatically. So if layout is RTL and
437      * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
438      */
resolveShouldLayoutReverse()439     private void resolveShouldLayoutReverse() {
440         // A == B is the same result, but we rather keep it readable
441         if (mOrientation == VERTICAL || !isLayoutRTL()) {
442             mShouldReverseLayout = mReverseLayout;
443         } else {
444             mShouldReverseLayout = !mReverseLayout;
445         }
446     }
447 
448     /**
449      * Returns if views are laid out from the opposite direction of the layout.
450      *
451      * @return If layout is reversed or not.
452      * @see #setReverseLayout(boolean)
453      */
getReverseLayout()454     public boolean getReverseLayout() {
455         return mReverseLayout;
456     }
457 
458     /**
459      * Used to reverse item traversal and layout order.
460      * This behaves similar to the layout change for RTL views. When set to true, first item is
461      * laid out at the end of the UI, second item is laid out before it etc.
462      *
463      * For horizontal layouts, it depends on the layout direction.
464      * When set to true, If {@link RecyclerView} is LTR, than it will
465      * layout from RTL, if {@link RecyclerView}} is RTL, it will layout
466      * from LTR.
467      *
468      * If you are looking for the exact same behavior of
469      * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
470      * {@link #setStackFromEnd(boolean)}
471      */
setReverseLayout(boolean reverseLayout)472     public void setReverseLayout(boolean reverseLayout) {
473         assertNotInLayoutOrScroll(null);
474         if (reverseLayout == mReverseLayout) {
475             return;
476         }
477         mReverseLayout = reverseLayout;
478         requestLayout();
479     }
480 
481     /**
482      * {@inheritDoc}
483      */
484     @Override
485     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
findViewByPosition(int position)486     public View findViewByPosition(int position) {
487         final int childCount = getChildCount();
488         if (childCount == 0) {
489             return null;
490         }
491         final int firstChild = getPosition(getChildAt(0));
492         final int viewPosition = position - firstChild;
493         if (viewPosition >= 0 && viewPosition < childCount) {
494             final View child = getChildAt(viewPosition);
495             if (getPosition(child) == position) {
496                 return child; // in pre-layout, this may not match
497             }
498         }
499         // fallback to traversal. This might be necessary in pre-layout.
500         return super.findViewByPosition(position);
501     }
502 
503     /**
504      * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
505      *
506      * <p>By default, {@link LinearLayoutManager} lays out 1 extra page
507      * of items while smooth scrolling and 0 otherwise. You can override this method to implement
508      * your custom layout pre-cache logic.</p>
509      *
510      * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
511      * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
512      * location, where 1) the extra content helps LinearLayoutManager know in advance when its
513      * target is approaching, so it can decelerate early and smoothly and 2) while motion is
514      * continuous.</p>
515      *
516      * <p>Extending the extra layout space is especially expensive if done while the user may change
517      * scrolling direction. Changing direction will cause the extra layout space to swap to the
518      * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
519      * enough to handle it.</p>
520      *
521      * @return The extra space that should be laid out (in pixels).
522      * @deprecated Use {@link #calculateExtraLayoutSpace(RecyclerView.State, int[])} instead.
523      */
524     @SuppressWarnings("DeprecatedIsStillUsed")
525     @Deprecated
getExtraLayoutSpace(RecyclerView.State state)526     protected int getExtraLayoutSpace(RecyclerView.State state) {
527         if (state.hasTargetScrollPosition()) {
528             return mOrientationHelper.getTotalSpace();
529         } else {
530             return 0;
531         }
532     }
533 
534     /**
535      * <p>Calculates the amount of extra space (in pixels) that should be laid out by {@link
536      * LinearLayoutManager} and stores the result in {@code extraLayoutSpace}. {@code
537      * extraLayoutSpace[0]} should be used for the extra space at the top/left, and {@code
538      * extraLayoutSpace[1]} should be used for the extra space at the bottom/right (depending on the
539      * orientation). Thus, the side where it is applied is unaffected by {@link
540      * #getLayoutDirection()} (LTR vs RTL), {@link #getStackFromEnd()} and {@link
541      * #getReverseLayout()}. Negative values are ignored.</p>
542      *
543      * <p>By default, {@code LinearLayoutManager} lays out 1 extra page of items while smooth
544      * scrolling, in the direction of the scroll, and no extra space is laid out in all other
545      * situations. You can override this method to implement your own custom pre-cache logic. Use
546      * {@link RecyclerView.State#hasTargetScrollPosition()} to find out if a smooth scroll to a
547      * position is in progress, and {@link RecyclerView.State#getTargetScrollPosition()} to find out
548      * which item it is scrolling to.</p>
549      *
550      * <p><strong>Note:</strong>Laying out extra items generally comes with significant performance
551      * cost. It's typically only desirable in places like smooth scrolling to an unknown location,
552      * where 1) the extra content helps LinearLayoutManager know in advance when its target is
553      * approaching, so it can decelerate early and smoothly and 2) while motion is continuous.</p>
554      *
555      * <p>Extending the extra layout space is especially expensive if done while the user may change
556      * scrolling direction. In the default implementation, changing direction will cause the extra
557      * layout space to swap to the opposite side of the viewport, incurring many rebinds/recycles,
558      * unless the cache is large enough to handle it.</p>
559      */
calculateExtraLayoutSpace(RecyclerView.@onNull State state, int @NonNull [] extraLayoutSpace)560     protected void calculateExtraLayoutSpace(RecyclerView.@NonNull State state,
561             int @NonNull [] extraLayoutSpace) {
562         int extraLayoutSpaceStart = 0;
563         int extraLayoutSpaceEnd = 0;
564 
565         // If calculateExtraLayoutSpace is not overridden, call the
566         // deprecated getExtraLayoutSpace for backwards compatibility
567         @SuppressWarnings("deprecation")
568         int extraScrollSpace = getExtraLayoutSpace(state);
569         if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
570             extraLayoutSpaceStart = extraScrollSpace;
571         } else {
572             extraLayoutSpaceEnd = extraScrollSpace;
573         }
574 
575         extraLayoutSpace[0] = extraLayoutSpaceStart;
576         extraLayoutSpace[1] = extraLayoutSpaceEnd;
577     }
578 
579     @Override
580     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)581     public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
582             int position) {
583         LinearSmoothScroller linearSmoothScroller =
584                 new LinearSmoothScroller(recyclerView.getContext());
585         linearSmoothScroller.setTargetPosition(position);
586         startSmoothScroll(linearSmoothScroller);
587     }
588 
589     @Override
590     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeScrollVectorForPosition(int targetPosition)591     public PointF computeScrollVectorForPosition(int targetPosition) {
592         if (getChildCount() == 0) {
593             return null;
594         }
595         final int firstChildPos = getPosition(getChildAt(0));
596         final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
597         if (mOrientation == HORIZONTAL) {
598             return new PointF(direction, 0);
599         } else {
600             return new PointF(0, direction);
601         }
602     }
603 
604     /**
605      * {@inheritDoc}
606      */
607     @Override
608     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
609     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
610         // layout algorithm:
611         // 1) by checking children and other variables, find an anchor coordinate and an anchor
612         //  item position.
613         // 2) fill towards start, stacking from bottom
614         // 3) fill towards end, stacking from top
615         // 4) scroll to fulfill requirements like stack from bottom.
616         // create layout state
617         if (DEBUG) {
618             Log.d(TAG, "is pre layout:" + state.isPreLayout());
619         }
620         if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
621             if (state.getItemCount() == 0) {
622                 removeAndRecycleAllViews(recycler);
623                 return;
624             }
625         }
626         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
627             mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
628         }
629 
630         ensureLayoutState();
631         mLayoutState.mRecycle = false;
632         // resolve layout direction
633         resolveShouldLayoutReverse();
634 
635         final View focused = getFocusedChild();
636         if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
637                 || mPendingSavedState != null) {
638             mAnchorInfo.reset();
639             mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
640             // calculate anchor position and coordinate
641             updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
642             mAnchorInfo.mValid = true;
643         } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
644                 >= mOrientationHelper.getEndAfterPadding()
645                 || mOrientationHelper.getDecoratedEnd(focused)
646                 <= mOrientationHelper.getStartAfterPadding())) {
647             // This case relates to when the anchor child is the focused view and due to layout
648             // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
649             // up after tapping an EditText which shrinks RV causing the focused view (The tapped
650             // EditText which is the anchor child) to get kicked out of the screen. Will update the
651             // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
652             // the available space in layoutState will be calculated as negative preventing the
653             // focused view from being laid out in fill.
654             // Note that we won't update the anchor position between layout passes (refer to
655             // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
656             // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
657             // child which can change between layout passes).
658             mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
659         }
660         if (DEBUG) {
661             Log.d(TAG, "Anchor info:" + mAnchorInfo);
662         }
663 
664         // LLM may decide to layout items for "extra" pixels to account for scrolling target,
665         // caching or predictive animations.
666 
667         mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
668                 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
669         mReusableIntPair[0] = 0;
670         mReusableIntPair[1] = 0;
671         calculateExtraLayoutSpace(state, mReusableIntPair);
672         int extraForStart = Math.max(0, mReusableIntPair[0])
673                 + mOrientationHelper.getStartAfterPadding();
674         int extraForEnd = Math.max(0, mReusableIntPair[1])
675                 + mOrientationHelper.getEndPadding();
676         if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
677                 && mPendingScrollPositionOffset != INVALID_OFFSET) {
678             // if the child is visible and we are going to move it around, we should layout
679             // extra items in the opposite direction to make sure new items animate nicely
680             // instead of just fading in
681             final View existing = findViewByPosition(mPendingScrollPosition);
682             if (existing != null) {
683                 final int current;
684                 final int upcomingOffset;
685                 if (mShouldReverseLayout) {
686                     current = mOrientationHelper.getEndAfterPadding()
687                             - mOrientationHelper.getDecoratedEnd(existing);
688                     upcomingOffset = current - mPendingScrollPositionOffset;
689                 } else {
690                     current = mOrientationHelper.getDecoratedStart(existing)
691                             - mOrientationHelper.getStartAfterPadding();
692                     upcomingOffset = mPendingScrollPositionOffset - current;
693                 }
694                 if (upcomingOffset > 0) {
695                     extraForStart += upcomingOffset;
696                 } else {
697                     extraForEnd -= upcomingOffset;
698                 }
699             }
700         }
701         int startOffset;
702         int endOffset;
703         final int firstLayoutDirection;
704         if (mAnchorInfo.mLayoutFromEnd) {
705             firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
706                     : LayoutState.ITEM_DIRECTION_HEAD;
707         } else {
708             firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
709                     : LayoutState.ITEM_DIRECTION_TAIL;
710         }
711 
712         onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
713         detachAndScrapAttachedViews(recycler);
714         mLayoutState.mInfinite = resolveIsInfinite();
715         mLayoutState.mIsPreLayout = state.isPreLayout();
716         // noRecycleSpace not needed: recycling doesn't happen in below's fill
717         // invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
718         mLayoutState.mNoRecycleSpace = 0;
719         if (mAnchorInfo.mLayoutFromEnd) {
720             // fill towards start
721             updateLayoutStateToFillStart(mAnchorInfo);
722             mLayoutState.mExtraFillSpace = extraForStart;
723             fill(recycler, mLayoutState, state, false);
724             startOffset = mLayoutState.mOffset;
725             final int firstElement = mLayoutState.mCurrentPosition;
726             if (mLayoutState.mAvailable > 0) {
727                 extraForEnd += mLayoutState.mAvailable;
728             }
729             // fill towards end
730             updateLayoutStateToFillEnd(mAnchorInfo);
731             mLayoutState.mExtraFillSpace = extraForEnd;
732             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
733             fill(recycler, mLayoutState, state, false);
734             endOffset = mLayoutState.mOffset;
735 
736             if (mLayoutState.mAvailable > 0) {
737                 // end could not consume all. add more items towards start
738                 extraForStart = mLayoutState.mAvailable;
739                 updateLayoutStateToFillStart(firstElement, startOffset);
740                 mLayoutState.mExtraFillSpace = extraForStart;
741                 fill(recycler, mLayoutState, state, false);
742                 startOffset = mLayoutState.mOffset;
743             }
744         } else {
745             // fill towards end
746             updateLayoutStateToFillEnd(mAnchorInfo);
747             mLayoutState.mExtraFillSpace = extraForEnd;
748             fill(recycler, mLayoutState, state, false);
749             endOffset = mLayoutState.mOffset;
750             final int lastElement = mLayoutState.mCurrentPosition;
751             if (mLayoutState.mAvailable > 0) {
752                 extraForStart += mLayoutState.mAvailable;
753             }
754             // fill towards start
755             updateLayoutStateToFillStart(mAnchorInfo);
756             mLayoutState.mExtraFillSpace = extraForStart;
757             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
758             fill(recycler, mLayoutState, state, false);
759             startOffset = mLayoutState.mOffset;
760 
761             if (mLayoutState.mAvailable > 0) {
762                 extraForEnd = mLayoutState.mAvailable;
763                 // start could not consume all it should. add more items towards end
764                 updateLayoutStateToFillEnd(lastElement, endOffset);
765                 mLayoutState.mExtraFillSpace = extraForEnd;
766                 fill(recycler, mLayoutState, state, false);
767                 endOffset = mLayoutState.mOffset;
768             }
769         }
770 
771         // changes may cause gaps on the UI, try to fix them.
772         // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
773         // changed
774         if (getChildCount() > 0) {
775             // because layout from end may be changed by scroll to position
776             // we re-calculate it.
777             // find which side we should check for gaps.
778             if (mShouldReverseLayout ^ mStackFromEnd) {
779                 int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
780                 startOffset += fixOffset;
781                 endOffset += fixOffset;
782                 fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
783                 startOffset += fixOffset;
784                 endOffset += fixOffset;
785             } else {
786                 int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
787                 startOffset += fixOffset;
788                 endOffset += fixOffset;
789                 fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
790                 startOffset += fixOffset;
791                 endOffset += fixOffset;
792             }
793         }
794         layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
795         if (!state.isPreLayout()) {
796             mOrientationHelper.onLayoutComplete();
797         } else {
798             mAnchorInfo.reset();
799         }
800         mLastStackFromEnd = mStackFromEnd;
801         if (DEBUG) {
802             validateChildOrder();
803         }
804     }
805 
806     @Override
807     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onLayoutCompleted(RecyclerView.State state)808     public void onLayoutCompleted(RecyclerView.State state) {
809         super.onLayoutCompleted(state);
810         mPendingSavedState = null; // we don't need this anymore
811         mPendingScrollPosition = RecyclerView.NO_POSITION;
812         mPendingScrollPositionOffset = INVALID_OFFSET;
813         mAnchorInfo.reset();
814     }
815 
816     /**
817      * Method called when Anchor position is decided. Extending class can setup accordingly or
818      * even update anchor info if necessary.
819      *
820      * @param recycler                 The recycler for the layout
821      * @param state                    The layout state
822      * @param anchorInfo               The mutable POJO that keeps the position and offset.
823      * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
824      *                                 indices.
825      */
onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int firstLayoutItemDirection)826     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
827             AnchorInfo anchorInfo, int firstLayoutItemDirection) {
828     }
829 
830     /**
831      * If necessary, layouts new items for predictive animations
832      */
layoutForPredictiveAnimations(RecyclerView.Recycler recycler, RecyclerView.State state, int startOffset, int endOffset)833     private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
834             RecyclerView.State state, int startOffset,
835             int endOffset) {
836         // If there are scrap children that we did not layout, we need to find where they did go
837         // and layout them accordingly so that animations can work as expected.
838         // This case may happen if new views are added or an existing view expands and pushes
839         // another view out of bounds.
840         if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout()
841                 || !supportsPredictiveItemAnimations()) {
842             return;
843         }
844         // to make the logic simpler, we calculate the size of children and call fill.
845         int scrapExtraStart = 0, scrapExtraEnd = 0;
846         final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
847         final int scrapSize = scrapList.size();
848         final int firstChildPos = getPosition(getChildAt(0));
849         for (int i = 0; i < scrapSize; i++) {
850             RecyclerView.ViewHolder scrap = scrapList.get(i);
851             if (scrap.isRemoved()) {
852                 continue;
853             }
854             final int position = scrap.getLayoutPosition();
855             final int direction = position < firstChildPos != mShouldReverseLayout
856                     ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
857             if (direction == LayoutState.LAYOUT_START) {
858                 scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
859             } else {
860                 scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
861             }
862         }
863 
864         if (DEBUG) {
865             Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart
866                     + " towards start and " + scrapExtraEnd + " towards end");
867         }
868         mLayoutState.mScrapList = scrapList;
869         if (scrapExtraStart > 0) {
870             View anchor = getChildClosestToStart();
871             updateLayoutStateToFillStart(getPosition(anchor), startOffset);
872             mLayoutState.mExtraFillSpace = scrapExtraStart;
873             mLayoutState.mAvailable = 0;
874             mLayoutState.assignPositionFromScrapList();
875             fill(recycler, mLayoutState, state, false);
876         }
877 
878         if (scrapExtraEnd > 0) {
879             View anchor = getChildClosestToEnd();
880             updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
881             mLayoutState.mExtraFillSpace = scrapExtraEnd;
882             mLayoutState.mAvailable = 0;
883             mLayoutState.assignPositionFromScrapList();
884             fill(recycler, mLayoutState, state, false);
885         }
886         mLayoutState.mScrapList = null;
887     }
888 
updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo)889     private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
890             AnchorInfo anchorInfo) {
891         if (updateAnchorFromPendingData(state, anchorInfo)) {
892             if (DEBUG) {
893                 Log.d(TAG, "updated anchor info from pending information");
894             }
895             return;
896         }
897 
898         if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
899             if (DEBUG) {
900                 Log.d(TAG, "updated anchor info from existing children");
901             }
902             return;
903         }
904         if (DEBUG) {
905             Log.d(TAG, "deciding anchor info for fresh state");
906         }
907         anchorInfo.assignCoordinateFromPadding();
908         anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
909     }
910 
911     /**
912      * Finds an anchor child from existing Views. Most of the time, this is the view closest to
913      * start or end that has a valid position (e.g. not removed).
914      * <p>
915      * If a child has focus, it is given priority.
916      */
updateAnchorFromChildren(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo)917     private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
918             RecyclerView.State state, AnchorInfo anchorInfo) {
919         if (getChildCount() == 0) {
920             return false;
921         }
922         final View focused = getFocusedChild();
923         if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
924             anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
925             return true;
926         }
927         if (mLastStackFromEnd != mStackFromEnd) {
928             return false;
929         }
930         View referenceChild =
931                 findReferenceChild(
932                         recycler,
933                         state,
934                         anchorInfo.mLayoutFromEnd,
935                         mStackFromEnd);
936         if (referenceChild != null) {
937             anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
938             // If all visible views are removed in 1 pass, reference child might be out of bounds.
939             // If that is the case, offset it back to 0 so that we use these pre-layout children.
940             if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
941                 // validate this child is at least partially visible. if not, offset it to start
942                 final int childStart = mOrientationHelper.getDecoratedStart(referenceChild);
943                 final int childEnd = mOrientationHelper.getDecoratedEnd(referenceChild);
944                 final int boundsStart = mOrientationHelper.getStartAfterPadding();
945                 final int boundsEnd = mOrientationHelper.getEndAfterPadding();
946                 // b/148869110: usually if childStart >= boundsEnd the child is out of
947                 // bounds, except if the child is 0 pixels!
948                 boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
949                 boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
950                 if (outOfBoundsBefore || outOfBoundsAfter) {
951                     anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd ? boundsEnd : boundsStart;
952                 }
953             }
954             return true;
955         }
956         return false;
957     }
958 
959     /**
960      * If there is a pending scroll position or saved states, updates the anchor info from that
961      * data and returns true
962      */
updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo)963     private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
964         if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
965             return false;
966         }
967         // validate scroll position
968         if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
969             mPendingScrollPosition = RecyclerView.NO_POSITION;
970             mPendingScrollPositionOffset = INVALID_OFFSET;
971             if (DEBUG) {
972                 Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
973             }
974             return false;
975         }
976 
977         // if child is visible, try to make it a reference child and ensure it is fully visible.
978         // if child is not visible, align it depending on its virtual position.
979         anchorInfo.mPosition = mPendingScrollPosition;
980         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
981             // Anchor offset depends on how that child was laid out. Here, we update it
982             // according to our current view bounds
983             anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
984             if (anchorInfo.mLayoutFromEnd) {
985                 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
986                         - mPendingSavedState.mAnchorOffset;
987             } else {
988                 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
989                         + mPendingSavedState.mAnchorOffset;
990             }
991             return true;
992         }
993 
994         if (mPendingScrollPositionOffset == INVALID_OFFSET) {
995             View child = findViewByPosition(mPendingScrollPosition);
996             if (child != null) {
997                 final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
998                 if (childSize > mOrientationHelper.getTotalSpace()) {
999                     // item does not fit. fix depending on layout direction
1000                     anchorInfo.assignCoordinateFromPadding();
1001                     return true;
1002                 }
1003                 final int startGap = mOrientationHelper.getDecoratedStart(child)
1004                         - mOrientationHelper.getStartAfterPadding();
1005                 if (startGap < 0) {
1006                     anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
1007                     anchorInfo.mLayoutFromEnd = false;
1008                     return true;
1009                 }
1010                 final int endGap = mOrientationHelper.getEndAfterPadding()
1011                         - mOrientationHelper.getDecoratedEnd(child);
1012                 if (endGap < 0) {
1013                     anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
1014                     anchorInfo.mLayoutFromEnd = true;
1015                     return true;
1016                 }
1017                 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
1018                         ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
1019                         .getTotalSpaceChange())
1020                         : mOrientationHelper.getDecoratedStart(child);
1021             } else { // item is not visible.
1022                 if (getChildCount() > 0) {
1023                     // get position of any child, does not matter
1024                     int pos = getPosition(getChildAt(0));
1025                     anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
1026                             == mShouldReverseLayout;
1027                 }
1028                 anchorInfo.assignCoordinateFromPadding();
1029             }
1030             return true;
1031         }
1032         // override layout from end values for consistency
1033         anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
1034         // if this changes, we should update prepareForDrop as well
1035         if (mShouldReverseLayout) {
1036             anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
1037                     - mPendingScrollPositionOffset;
1038         } else {
1039             anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
1040                     + mPendingScrollPositionOffset;
1041         }
1042         return true;
1043     }
1044 
1045     /**
1046      * @return The final offset amount for children
1047      */
1048     private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
1049             RecyclerView.State state, boolean canOffsetChildren) {
1050         int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
1051         int fixOffset = 0;
1052         if (gap > 0) {
1053             fixOffset = -scrollBy(-gap, recycler, state);
1054         } else {
1055             return 0; // nothing to fix
1056         }
1057         // move offset according to scroll amount
1058         endOffset += fixOffset;
1059         if (canOffsetChildren) {
1060             // re-calculate gap, see if we could fix it
1061             gap = mOrientationHelper.getEndAfterPadding() - endOffset;
1062             if (gap > 0) {
1063                 mOrientationHelper.offsetChildren(gap);
1064                 return gap + fixOffset;
1065             }
1066         }
1067         return fixOffset;
1068     }
1069 
1070     /**
1071      * @return The final offset amount for children
1072      */
fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, RecyclerView.State state, boolean canOffsetChildren)1073     private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
1074             RecyclerView.State state, boolean canOffsetChildren) {
1075         int gap = startOffset - mOrientationHelper.getStartAfterPadding();
1076         int fixOffset = 0;
1077         if (gap > 0) {
1078             // check if we should fix this gap.
1079             fixOffset = -scrollBy(gap, recycler, state);
1080         } else {
1081             return 0; // nothing to fix
1082         }
1083         startOffset += fixOffset;
1084         if (canOffsetChildren) {
1085             // re-calculate gap, see if we could fix it
1086             gap = startOffset - mOrientationHelper.getStartAfterPadding();
1087             if (gap > 0) {
1088                 mOrientationHelper.offsetChildren(-gap);
1089                 return fixOffset - gap;
1090             }
1091         }
1092         return fixOffset;
1093     }
1094 
updateLayoutStateToFillEnd(AnchorInfo anchorInfo)1095     private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
1096         updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
1097     }
1098 
updateLayoutStateToFillEnd(int itemPosition, int offset)1099     private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
1100         mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
1101         mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
1102                 LayoutState.ITEM_DIRECTION_TAIL;
1103         mLayoutState.mCurrentPosition = itemPosition;
1104         mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
1105         mLayoutState.mOffset = offset;
1106         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
1107     }
1108 
updateLayoutStateToFillStart(AnchorInfo anchorInfo)1109     private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) {
1110         updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
1111     }
1112 
updateLayoutStateToFillStart(int itemPosition, int offset)1113     private void updateLayoutStateToFillStart(int itemPosition, int offset) {
1114         mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
1115         mLayoutState.mCurrentPosition = itemPosition;
1116         mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
1117                 LayoutState.ITEM_DIRECTION_HEAD;
1118         mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
1119         mLayoutState.mOffset = offset;
1120         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
1121 
1122     }
1123 
isLayoutRTL()1124     protected boolean isLayoutRTL() {
1125         return getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1126     }
1127 
ensureLayoutState()1128     void ensureLayoutState() {
1129         if (mLayoutState == null) {
1130             mLayoutState = createLayoutState();
1131         }
1132     }
1133 
1134     /**
1135      * Test overrides this to plug some tracking and verification.
1136      *
1137      * @return A new LayoutState
1138      */
createLayoutState()1139     LayoutState createLayoutState() {
1140         return new LayoutState();
1141     }
1142 
1143     /**
1144      * <p>Scroll the RecyclerView to make the position visible.</p>
1145      *
1146      * <p>RecyclerView will scroll the minimum amount that is necessary to make the
1147      * target position visible. If you are looking for a similar behavior to
1148      * {@link android.widget.ListView#setSelection(int)} or
1149      * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
1150      * {@link #scrollToPositionWithOffset(int, int)}.</p>
1151      *
1152      * <p>Note that scroll position change will not be reflected until the next layout call.</p>
1153      *
1154      * @param position Scroll to this adapter position
1155      * @see #scrollToPositionWithOffset(int, int)
1156      */
1157     @Override
scrollToPosition(int position)1158     public void scrollToPosition(int position) {
1159         mPendingScrollPosition = position;
1160         mPendingScrollPositionOffset = INVALID_OFFSET;
1161         if (mPendingSavedState != null) {
1162             mPendingSavedState.invalidateAnchor();
1163         }
1164         requestLayout();
1165     }
1166 
1167     /**
1168      * Scroll to the specified adapter position with the given offset from resolved layout
1169      * start. Resolved layout start depends on {@link #getReverseLayout()},
1170      * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}.
1171      * <p>
1172      * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
1173      * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
1174      * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
1175      * <p>
1176      * Note that scroll position change will not be reflected until the next layout call.
1177      * <p>
1178      * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
1179      *
1180      * @param position Index (starting at 0) of the reference item.
1181      * @param offset   The distance (in pixels) between the start edge of the item view and
1182      *                 start edge of the RecyclerView.
1183      * @see #setReverseLayout(boolean)
1184      * @see #scrollToPosition(int)
1185      */
scrollToPositionWithOffset(int position, int offset)1186     public void scrollToPositionWithOffset(int position, int offset) {
1187         mPendingScrollPosition = position;
1188         mPendingScrollPositionOffset = offset;
1189         if (mPendingSavedState != null) {
1190             mPendingSavedState.invalidateAnchor();
1191         }
1192         requestLayout();
1193     }
1194 
1195 
1196     /**
1197      * {@inheritDoc}
1198      */
1199     @Override
1200     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)1201     public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
1202             RecyclerView.State state) {
1203         if (mOrientation == VERTICAL) {
1204             return 0;
1205         }
1206         return scrollBy(dx, recycler, state);
1207     }
1208 
1209     /**
1210      * {@inheritDoc}
1211      */
1212     @Override
1213     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)1214     public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
1215             RecyclerView.State state) {
1216         if (mOrientation == HORIZONTAL) {
1217             return 0;
1218         }
1219         return scrollBy(dy, recycler, state);
1220     }
1221 
1222     @Override
1223     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeHorizontalScrollOffset(RecyclerView.State state)1224     public int computeHorizontalScrollOffset(RecyclerView.State state) {
1225         return computeScrollOffset(state);
1226     }
1227 
1228     @Override
1229     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeVerticalScrollOffset(RecyclerView.State state)1230     public int computeVerticalScrollOffset(RecyclerView.State state) {
1231         return computeScrollOffset(state);
1232     }
1233 
1234     @Override
1235     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeHorizontalScrollExtent(RecyclerView.State state)1236     public int computeHorizontalScrollExtent(RecyclerView.State state) {
1237         return computeScrollExtent(state);
1238     }
1239 
1240     @Override
1241     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeVerticalScrollExtent(RecyclerView.State state)1242     public int computeVerticalScrollExtent(RecyclerView.State state) {
1243         return computeScrollExtent(state);
1244     }
1245 
1246     @Override
1247     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeHorizontalScrollRange(RecyclerView.State state)1248     public int computeHorizontalScrollRange(RecyclerView.State state) {
1249         return computeScrollRange(state);
1250     }
1251 
1252     @Override
1253     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
computeVerticalScrollRange(RecyclerView.State state)1254     public int computeVerticalScrollRange(RecyclerView.State state) {
1255         return computeScrollRange(state);
1256     }
1257 
computeScrollOffset(RecyclerView.State state)1258     private int computeScrollOffset(RecyclerView.State state) {
1259         if (getChildCount() == 0) {
1260             return 0;
1261         }
1262         ensureLayoutState();
1263         return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
1264                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
1265                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
1266                 this, mSmoothScrollbarEnabled, mShouldReverseLayout);
1267     }
1268 
computeScrollExtent(RecyclerView.State state)1269     private int computeScrollExtent(RecyclerView.State state) {
1270         if (getChildCount() == 0) {
1271             return 0;
1272         }
1273         ensureLayoutState();
1274         return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
1275                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
1276                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
1277                 this, mSmoothScrollbarEnabled);
1278     }
1279 
computeScrollRange(RecyclerView.State state)1280     private int computeScrollRange(RecyclerView.State state) {
1281         if (getChildCount() == 0) {
1282             return 0;
1283         }
1284         ensureLayoutState();
1285         return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
1286                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
1287                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
1288                 this, mSmoothScrollbarEnabled);
1289     }
1290 
1291     /**
1292      * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
1293      * based on the number of visible pixels in the visible items. This however assumes that all
1294      * list items have similar or equal widths or heights (depending on list orientation).
1295      * If you use a list in which items have different dimensions, the scrollbar will change
1296      * appearance as the user scrolls through the list. To avoid this issue,  you need to disable
1297      * this property.
1298      *
1299      * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
1300      * solely on the number of items in the adapter and the position of the visible items inside
1301      * the adapter. This provides a stable scrollbar as the user navigates through a list of items
1302      * with varying widths / heights.
1303      *
1304      * @param enabled Whether or not to enable smooth scrollbar.
1305      * @see #setSmoothScrollbarEnabled(boolean)
1306      */
setSmoothScrollbarEnabled(boolean enabled)1307     public void setSmoothScrollbarEnabled(boolean enabled) {
1308         mSmoothScrollbarEnabled = enabled;
1309     }
1310 
1311     /**
1312      * Returns the current state of the smooth scrollbar feature. It is enabled by default.
1313      *
1314      * @return True if smooth scrollbar is enabled, false otherwise.
1315      * @see #setSmoothScrollbarEnabled(boolean)
1316      */
isSmoothScrollbarEnabled()1317     public boolean isSmoothScrollbarEnabled() {
1318         return mSmoothScrollbarEnabled;
1319     }
1320 
updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state)1321     private void updateLayoutState(int layoutDirection, int requiredSpace,
1322             boolean canUseExistingSpace, RecyclerView.State state) {
1323         // If parent provides a hint, don't measure unlimited.
1324         mLayoutState.mInfinite = resolveIsInfinite();
1325         mLayoutState.mLayoutDirection = layoutDirection;
1326         mReusableIntPair[0] = 0;
1327         mReusableIntPair[1] = 0;
1328         calculateExtraLayoutSpace(state, mReusableIntPair);
1329         int extraForStart = Math.max(0, mReusableIntPair[0]);
1330         int extraForEnd = Math.max(0, mReusableIntPair[1]);
1331         boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
1332         mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
1333         mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
1334         int scrollingOffset;
1335         if (layoutToEnd) {
1336             mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
1337             // get the first child in the direction we are going
1338             final View child = getChildClosestToEnd();
1339             // the direction in which we are traversing children
1340             mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
1341                     : LayoutState.ITEM_DIRECTION_TAIL;
1342             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
1343             mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
1344             // calculate how much we can scroll without adding new children (independent of layout)
1345             scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
1346                     - mOrientationHelper.getEndAfterPadding();
1347 
1348         } else {
1349             final View child = getChildClosestToStart();
1350             mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding();
1351             mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
1352                     : LayoutState.ITEM_DIRECTION_HEAD;
1353             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
1354             mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
1355             scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
1356                     + mOrientationHelper.getStartAfterPadding();
1357         }
1358         mLayoutState.mAvailable = requiredSpace;
1359         if (canUseExistingSpace) {
1360             mLayoutState.mAvailable -= scrollingOffset;
1361         }
1362         mLayoutState.mScrollingOffset = scrollingOffset;
1363     }
1364 
resolveIsInfinite()1365     boolean resolveIsInfinite() {
1366         return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
1367                 && mOrientationHelper.getEnd() == 0;
1368     }
1369 
collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, LayoutPrefetchRegistry layoutPrefetchRegistry)1370     void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
1371             LayoutPrefetchRegistry layoutPrefetchRegistry) {
1372         final int pos = layoutState.mCurrentPosition;
1373         if (pos >= 0 && pos < state.getItemCount()) {
1374             layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
1375         }
1376     }
1377 
1378     @Override
1379     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
collectInitialPrefetchPositions(int adapterItemCount, LayoutPrefetchRegistry layoutPrefetchRegistry)1380     public void collectInitialPrefetchPositions(int adapterItemCount,
1381             LayoutPrefetchRegistry layoutPrefetchRegistry) {
1382         final boolean fromEnd;
1383         final int anchorPos;
1384         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
1385             // use restored state, since it hasn't been resolved yet
1386             fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
1387             anchorPos = mPendingSavedState.mAnchorPosition;
1388         } else {
1389             resolveShouldLayoutReverse();
1390             fromEnd = mShouldReverseLayout;
1391             if (mPendingScrollPosition == RecyclerView.NO_POSITION) {
1392                 anchorPos = fromEnd ? adapterItemCount - 1 : 0;
1393             } else {
1394                 anchorPos = mPendingScrollPosition;
1395             }
1396         }
1397 
1398         final int direction = fromEnd
1399                 ? LayoutState.ITEM_DIRECTION_HEAD
1400                 : LayoutState.ITEM_DIRECTION_TAIL;
1401         int targetPos = anchorPos;
1402         for (int i = 0; i < mInitialPrefetchItemCount; i++) {
1403             if (targetPos >= 0 && targetPos < adapterItemCount) {
1404                 layoutPrefetchRegistry.addPosition(targetPos, 0);
1405             } else {
1406                 break; // no more to prefetch
1407             }
1408             targetPos += direction;
1409         }
1410     }
1411 
1412     /**
1413      * Sets the number of items to prefetch in
1414      * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
1415      * how many inner items should be prefetched when this LayoutManager's RecyclerView
1416      * is nested inside another RecyclerView.
1417      *
1418      * <p>Set this value to the number of items this inner LayoutManager will display when it is
1419      * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
1420      * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.</p>
1421      *
1422      * <p>For example, take a vertically scrolling RecyclerView with horizontally scrolling inner
1423      * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing
1424      * <code>4</code> to this method for each inner RecyclerView's LinearLayoutManager will enable
1425      * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early,
1426      * before it is scrolled on screen, instead of just the default 2.</p>
1427      *
1428      * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
1429      * nested in another RecyclerView.</p>
1430      *
1431      * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
1432      * views that will be visible in this view can incur unnecessary bind work, and an increase to
1433      * the number of Views created and in active use.</p>
1434      *
1435      * @param itemCount Number of items to prefetch
1436      * @see #isItemPrefetchEnabled()
1437      * @see #getInitialPrefetchItemCount()
1438      * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
1439      */
setInitialPrefetchItemCount(int itemCount)1440     public void setInitialPrefetchItemCount(int itemCount) {
1441         mInitialPrefetchItemCount = itemCount;
1442     }
1443 
1444     /**
1445      * Gets the number of items to prefetch in
1446      * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
1447      * how many inner items should be prefetched when this LayoutManager's RecyclerView
1448      * is nested inside another RecyclerView.
1449      *
1450      * @return number of items to prefetch.
1451      * @see #isItemPrefetchEnabled()
1452      * @see #setInitialPrefetchItemCount(int)
1453      * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
1454      */
getInitialPrefetchItemCount()1455     public int getInitialPrefetchItemCount() {
1456         return mInitialPrefetchItemCount;
1457     }
1458 
1459     @Override
1460     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, LayoutPrefetchRegistry layoutPrefetchRegistry)1461     public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
1462             LayoutPrefetchRegistry layoutPrefetchRegistry) {
1463         int delta = (mOrientation == HORIZONTAL) ? dx : dy;
1464         if (getChildCount() == 0 || delta == 0) {
1465             // can't support this scroll, so don't bother prefetching
1466             return;
1467         }
1468 
1469         ensureLayoutState();
1470         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
1471         final int absDelta = Math.abs(delta);
1472         updateLayoutState(layoutDirection, absDelta, true, state);
1473         collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
1474     }
1475 
scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state)1476     int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
1477         if (getChildCount() == 0 || delta == 0) {
1478             return 0;
1479         }
1480         ensureLayoutState();
1481         mLayoutState.mRecycle = true;
1482         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
1483         final int absDelta = Math.abs(delta);
1484         updateLayoutState(layoutDirection, absDelta, true, state);
1485         final int consumed = mLayoutState.mScrollingOffset
1486                 + fill(recycler, mLayoutState, state, false);
1487         if (consumed < 0) {
1488             if (DEBUG) {
1489                 Log.d(TAG, "Don't have any more elements to scroll");
1490             }
1491             return 0;
1492         }
1493         final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
1494         mOrientationHelper.offsetChildren(-scrolled);
1495         if (DEBUG) {
1496             Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
1497         }
1498         mLayoutState.mLastScrollDelta = scrolled;
1499         return scrolled;
1500     }
1501 
1502     @Override
1503     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
assertNotInLayoutOrScroll(String message)1504     public void assertNotInLayoutOrScroll(String message) {
1505         if (mPendingSavedState == null) {
1506             super.assertNotInLayoutOrScroll(message);
1507         }
1508     }
1509 
1510     /**
1511      * Recycles children between given indices.
1512      *
1513      * @param startIndex inclusive
1514      * @param endIndex   exclusive
1515      */
recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex)1516     private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
1517         if (startIndex == endIndex) {
1518             return;
1519         }
1520         if (DEBUG) {
1521             Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
1522         }
1523         if (endIndex > startIndex) {
1524             for (int i = endIndex - 1; i >= startIndex; i--) {
1525                 removeAndRecycleViewAt(i, recycler);
1526             }
1527         } else {
1528             for (int i = startIndex; i > endIndex; i--) {
1529                 removeAndRecycleViewAt(i, recycler);
1530             }
1531         }
1532     }
1533 
1534     /**
1535      * Recycles views that went out of bounds after scrolling towards the end of the layout.
1536      * <p>
1537      * Checks both layout position and visible position to guarantee that the view is not visible.
1538      *
1539      * @param recycler        Recycler instance of {@link RecyclerView}
1540      * @param scrollingOffset This can be used to add additional padding to the visible area. This
1541      *                        is used to detect children that will go out of bounds after scrolling,
1542      *                        without actually moving them.
1543      * @param noRecycleSpace  Extra space that should be excluded from recycling. This is the space
1544      *                        from {@code extraLayoutSpace[0]}, calculated in {@link
1545      *                        #calculateExtraLayoutSpace}.
1546      */
recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset, int noRecycleSpace)1547     private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
1548             int noRecycleSpace) {
1549         if (scrollingOffset < 0) {
1550             if (DEBUG) {
1551                 Log.d(TAG, "Called recycle from start with a negative value. This might happen"
1552                         + " during layout changes but may be sign of a bug");
1553             }
1554             return;
1555         }
1556         // ignore padding, ViewGroup may not clip children.
1557         final int limit = scrollingOffset - noRecycleSpace;
1558         final int childCount = getChildCount();
1559         if (mShouldReverseLayout) {
1560             for (int i = childCount - 1; i >= 0; i--) {
1561                 View child = getChildAt(i);
1562                 if (mOrientationHelper.getDecoratedEnd(child) > limit
1563                         || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
1564                     // stop here
1565                     recycleChildren(recycler, childCount - 1, i);
1566                     return;
1567                 }
1568             }
1569         } else {
1570             for (int i = 0; i < childCount; i++) {
1571                 View child = getChildAt(i);
1572                 if (mOrientationHelper.getDecoratedEnd(child) > limit
1573                         || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
1574                     // stop here
1575                     recycleChildren(recycler, 0, i);
1576                     return;
1577                 }
1578             }
1579         }
1580     }
1581 
1582 
1583     /**
1584      * Recycles views that went out of bounds after scrolling towards the start of the layout.
1585      * <p>
1586      * Checks both layout position and visible position to guarantee that the view is not visible.
1587      *
1588      * @param recycler        Recycler instance of {@link RecyclerView}
1589      * @param scrollingOffset This can be used to add additional padding to the visible area. This
1590      *                        is used to detect children that will go out of bounds after scrolling,
1591      *                        without actually moving them.
1592      * @param noRecycleSpace  Extra space that should be excluded from recycling. This is the space
1593      *                        from {@code extraLayoutSpace[1]}, calculated in {@link
1594      *                        #calculateExtraLayoutSpace}.
1595      */
recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset, int noRecycleSpace)1596     private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
1597             int noRecycleSpace) {
1598         final int childCount = getChildCount();
1599         if (scrollingOffset < 0) {
1600             if (DEBUG) {
1601                 Log.d(TAG, "Called recycle from end with a negative value. This might happen"
1602                         + " during layout changes but may be sign of a bug");
1603             }
1604             return;
1605         }
1606         final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
1607         if (mShouldReverseLayout) {
1608             for (int i = 0; i < childCount; i++) {
1609                 View child = getChildAt(i);
1610                 if (mOrientationHelper.getDecoratedStart(child) < limit
1611                         || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
1612                     // stop here
1613                     recycleChildren(recycler, 0, i);
1614                     return;
1615                 }
1616             }
1617         } else {
1618             for (int i = childCount - 1; i >= 0; i--) {
1619                 View child = getChildAt(i);
1620                 if (mOrientationHelper.getDecoratedStart(child) < limit
1621                         || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
1622                     // stop here
1623                     recycleChildren(recycler, childCount - 1, i);
1624                     return;
1625                 }
1626             }
1627         }
1628     }
1629 
1630     /**
1631      * Helper method to call appropriate recycle method depending on current layout direction
1632      *
1633      * @param recycler    Current recycler that is attached to RecyclerView
1634      * @param layoutState Current layout state. Right now, this object does not change but
1635      *                    we may consider moving it out of this view so passing around as a
1636      *                    parameter for now, rather than accessing {@link #mLayoutState}
1637      * @see #recycleViewsFromStart(RecyclerView.Recycler, int, int)
1638      * @see #recycleViewsFromEnd(RecyclerView.Recycler, int, int)
1639      * @see LinearLayoutManager.LayoutState#mLayoutDirection
1640      */
recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState)1641     private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
1642         if (!layoutState.mRecycle || layoutState.mInfinite) {
1643             return;
1644         }
1645         int scrollingOffset = layoutState.mScrollingOffset;
1646         int noRecycleSpace = layoutState.mNoRecycleSpace;
1647         if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1648             recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
1649         } else {
1650             recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
1651         }
1652     }
1653 
1654     /**
1655      * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
1656      * independent from the rest of the {@link LinearLayoutManager}
1657      * and with little change, can be made publicly available as a helper class.
1658      *
1659      * @param recycler        Current recycler that is attached to RecyclerView
1660      * @param layoutState     Configuration on how we should fill out the available space.
1661      * @param state           Context passed by the RecyclerView to control scroll steps.
1662      * @param stopOnFocusable If true, filling stops in the first focusable new child
1663      * @return Number of pixels that it added. Useful for scroll functions.
1664      */
fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)1665     int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
1666             RecyclerView.State state, boolean stopOnFocusable) {
1667         // max offset we should set is mFastScroll + available
1668         final int start = layoutState.mAvailable;
1669         if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
1670             // TODO ugly bug fix. should not happen
1671             if (layoutState.mAvailable < 0) {
1672                 layoutState.mScrollingOffset += layoutState.mAvailable;
1673             }
1674             recycleByLayoutState(recycler, layoutState);
1675         }
1676         int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
1677         LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
1678         while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
1679             layoutChunkResult.resetInternal();
1680             if (RecyclerView.VERBOSE_TRACING) {
1681                 Trace.beginSection("LLM LayoutChunk");
1682             }
1683             layoutChunk(recycler, state, layoutState, layoutChunkResult);
1684             if (RecyclerView.VERBOSE_TRACING) {
1685                 Trace.endSection();
1686             }
1687             if (layoutChunkResult.mFinished) {
1688                 break;
1689             }
1690             layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
1691             /*
1692              * Consume the available space if:
1693              * * layoutChunk did not request to be ignored
1694              * * OR we are laying out scrap children
1695              * * OR we are not doing pre-layout
1696              */
1697             if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
1698                     || !state.isPreLayout()) {
1699                 layoutState.mAvailable -= layoutChunkResult.mConsumed;
1700                 // we keep a separate remaining space because mAvailable is important for recycling
1701                 remainingSpace -= layoutChunkResult.mConsumed;
1702             }
1703 
1704             if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
1705                 layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
1706                 if (layoutState.mAvailable < 0) {
1707                     layoutState.mScrollingOffset += layoutState.mAvailable;
1708                 }
1709                 recycleByLayoutState(recycler, layoutState);
1710             }
1711             if (stopOnFocusable && layoutChunkResult.mFocusable) {
1712                 break;
1713             }
1714         }
1715         if (DEBUG) {
1716             validateChildOrder();
1717         }
1718         return start - layoutState.mAvailable;
1719     }
1720 
layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result)1721     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
1722             LayoutState layoutState, LayoutChunkResult result) {
1723         View view = layoutState.next(recycler);
1724         if (view == null) {
1725             if (DEBUG && layoutState.mScrapList == null) {
1726                 throw new RuntimeException("received null view when unexpected");
1727             }
1728             // if we are laying out views in scrap, this may return null which means there is
1729             // no more items to layout.
1730             result.mFinished = true;
1731             return;
1732         }
1733         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
1734         if (layoutState.mScrapList == null) {
1735             if (mShouldReverseLayout == (layoutState.mLayoutDirection
1736                     == LayoutState.LAYOUT_START)) {
1737                 addView(view);
1738             } else {
1739                 addView(view, 0);
1740             }
1741         } else {
1742             if (mShouldReverseLayout == (layoutState.mLayoutDirection
1743                     == LayoutState.LAYOUT_START)) {
1744                 addDisappearingView(view);
1745             } else {
1746                 addDisappearingView(view, 0);
1747             }
1748         }
1749         measureChildWithMargins(view, 0, 0);
1750         result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
1751         int left, top, right, bottom;
1752         if (mOrientation == VERTICAL) {
1753             if (isLayoutRTL()) {
1754                 right = getWidth() - getPaddingRight();
1755                 left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
1756             } else {
1757                 left = getPaddingLeft();
1758                 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
1759             }
1760             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1761                 bottom = layoutState.mOffset;
1762                 top = layoutState.mOffset - result.mConsumed;
1763             } else {
1764                 top = layoutState.mOffset;
1765                 bottom = layoutState.mOffset + result.mConsumed;
1766             }
1767         } else {
1768             top = getPaddingTop();
1769             bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
1770 
1771             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1772                 right = layoutState.mOffset;
1773                 left = layoutState.mOffset - result.mConsumed;
1774             } else {
1775                 left = layoutState.mOffset;
1776                 right = layoutState.mOffset + result.mConsumed;
1777             }
1778         }
1779         // We calculate everything with View's bounding box (which includes decor and margins)
1780         // To calculate correct layout position, we subtract margins.
1781         layoutDecoratedWithMargins(view, left, top, right, bottom);
1782         if (DEBUG) {
1783             Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
1784                     + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
1785                     + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
1786         }
1787         // Consume the available space if the view is not removed OR changed
1788         if (params.isItemRemoved() || params.isItemChanged()) {
1789             result.mIgnoreConsumed = true;
1790         }
1791         result.mFocusable = view.hasFocusable();
1792     }
1793 
1794     @Override
shouldMeasureTwice()1795     boolean shouldMeasureTwice() {
1796         return getHeightMode() != View.MeasureSpec.EXACTLY
1797                 && getWidthMode() != View.MeasureSpec.EXACTLY
1798                 && hasFlexibleChildInBothOrientations();
1799     }
1800 
1801     /**
1802      * Converts a focusDirection to orientation.
1803      *
1804      * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
1805      *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
1806      *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
1807      *                       or 0 for not applicable
1808      * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
1809      * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
1810      */
convertFocusDirectionToLayoutDirection(int focusDirection)1811     int convertFocusDirectionToLayoutDirection(int focusDirection) {
1812         switch (focusDirection) {
1813             case View.FOCUS_BACKWARD:
1814                 if (mOrientation == VERTICAL) {
1815                     return LayoutState.LAYOUT_START;
1816                 } else if (isLayoutRTL()) {
1817                     return LayoutState.LAYOUT_END;
1818                 } else {
1819                     return LayoutState.LAYOUT_START;
1820                 }
1821             case View.FOCUS_FORWARD:
1822                 if (mOrientation == VERTICAL) {
1823                     return LayoutState.LAYOUT_END;
1824                 } else if (isLayoutRTL()) {
1825                     return LayoutState.LAYOUT_START;
1826                 } else {
1827                     return LayoutState.LAYOUT_END;
1828                 }
1829             case View.FOCUS_UP:
1830                 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
1831                         : LayoutState.INVALID_LAYOUT;
1832             case View.FOCUS_DOWN:
1833                 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
1834                         : LayoutState.INVALID_LAYOUT;
1835             case View.FOCUS_LEFT:
1836                 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
1837                         : LayoutState.INVALID_LAYOUT;
1838             case View.FOCUS_RIGHT:
1839                 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
1840                         : LayoutState.INVALID_LAYOUT;
1841             default:
1842                 if (DEBUG) {
1843                     Log.d(TAG, "Unknown focus request:" + focusDirection);
1844                 }
1845                 return LayoutState.INVALID_LAYOUT;
1846         }
1847 
1848     }
1849 
1850     /**
1851      * Convenience method to find the child closes to start. Caller should check it has enough
1852      * children.
1853      *
1854      * @return The child closes to start of the layout from user's perspective.
1855      */
getChildClosestToStart()1856     private View getChildClosestToStart() {
1857         return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
1858     }
1859 
1860     /**
1861      * Convenience method to find the child closes to end. Caller should check it has enough
1862      * children.
1863      *
1864      * @return The child closes to end of the layout from user's perspective.
1865      */
getChildClosestToEnd()1866     private View getChildClosestToEnd() {
1867         return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
1868     }
1869 
1870     /**
1871      * Convenience method to find the visible child closes to start. Caller should check if it has
1872      * enough children.
1873      *
1874      * @param completelyVisible Whether child should be completely visible or not
1875      * @return The first visible child closest to start of the layout from user's perspective.
1876      */
findFirstVisibleChildClosestToStart(boolean completelyVisible, boolean acceptPartiallyVisible)1877     View findFirstVisibleChildClosestToStart(boolean completelyVisible,
1878             boolean acceptPartiallyVisible) {
1879         if (mShouldReverseLayout) {
1880             return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
1881                     acceptPartiallyVisible);
1882         } else {
1883             return findOneVisibleChild(0, getChildCount(), completelyVisible,
1884                     acceptPartiallyVisible);
1885         }
1886     }
1887 
1888     /**
1889      * Convenience method to find the visible child closes to end. Caller should check if it has
1890      * enough children.
1891      *
1892      * @param completelyVisible Whether child should be completely visible or not
1893      * @return The first visible child closest to end of the layout from user's perspective.
1894      */
findFirstVisibleChildClosestToEnd(boolean completelyVisible, boolean acceptPartiallyVisible)1895     View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
1896             boolean acceptPartiallyVisible) {
1897         if (mShouldReverseLayout) {
1898             return findOneVisibleChild(0, getChildCount(), completelyVisible,
1899                     acceptPartiallyVisible);
1900         } else {
1901             return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
1902                     acceptPartiallyVisible);
1903         }
1904     }
1905 
1906     // overridden by GridLayoutManager
1907 
1908     /**
1909      * Finds a suitable anchor child.
1910      * <p>
1911      * Due to ambiguous adapter updates or children being removed, some children's positions may be
1912      * invalid. This method is a best effort to find a position within adapter bounds if possible.
1913      * <p>
1914      * It also prioritizes children from best to worst in this order:
1915      * <ol>
1916      *   <li> An in bounds child.
1917      *   <li> An out of bounds child.
1918      *   <li> An invalid child.
1919      * </ol>
1920      *
1921      * @param layoutFromEnd True if the RV scrolls in the reverse direction, which is the same as
1922      *                      (reverseLayout ^ stackFromEnd).
1923      * @param traverseChildrenInReverseOrder True if the children should be traversed in reverse
1924      *                                       order (stackFromEnd).
1925      * @return A View that can be used an an anchor View.
1926      */
findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, boolean layoutFromEnd, boolean traverseChildrenInReverseOrder)1927     View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
1928             boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) {
1929         ensureLayoutState();
1930 
1931         // Determine which direction through the view children we are going iterate.
1932         int start = 0;
1933         int end = getChildCount();
1934         int diff = 1;
1935         if (traverseChildrenInReverseOrder) {
1936             start = getChildCount() - 1;
1937             end = -1;
1938             diff = -1;
1939         }
1940 
1941         int itemCount = state.getItemCount();
1942 
1943         final int boundsStart = mOrientationHelper.getStartAfterPadding();
1944         final int boundsEnd = mOrientationHelper.getEndAfterPadding();
1945 
1946         View invalidMatch = null;
1947         View bestFirstFind = null;
1948         View bestSecondFind = null;
1949 
1950         for (int i = start; i != end; i += diff) {
1951             final View view = getChildAt(i);
1952             final int position = getPosition(view);
1953             final int childStart = mOrientationHelper.getDecoratedStart(view);
1954             final int childEnd = mOrientationHelper.getDecoratedEnd(view);
1955             if (position >= 0 && position < itemCount) {
1956                 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
1957                     if (invalidMatch == null) {
1958                         invalidMatch = view; // removed item, least preferred
1959                     }
1960                 } else {
1961                     // b/148869110: usually if childStart >= boundsEnd the child is out of
1962                     // bounds, except if the child is 0 pixels!
1963                     boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
1964                     boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
1965                     if (outOfBoundsBefore || outOfBoundsAfter) {
1966                         // The item is out of bounds.
1967                         // We want to find the items closest to the in bounds items and because we
1968                         // are always going through the items linearly, the 2 items we want are the
1969                         // last out of bounds item on the side we start searching on, and the first
1970                         // out of bounds item on the side we are ending on.  The side that we are
1971                         // ending on ultimately takes priority because we want items later in the
1972                         // layout to move forward if no in bounds anchors are found.
1973                         if (layoutFromEnd) {
1974                             if (outOfBoundsAfter) {
1975                                 bestFirstFind = view;
1976                             } else if (bestSecondFind == null) {
1977                                 bestSecondFind = view;
1978                             }
1979                         } else {
1980                             if (outOfBoundsBefore) {
1981                                 bestFirstFind = view;
1982                             } else if (bestSecondFind == null) {
1983                                 bestSecondFind = view;
1984                             }
1985                         }
1986                     } else {
1987                         // We found an in bounds item, greedily return it.
1988                         return view;
1989                     }
1990                 }
1991             }
1992         }
1993         // We didn't find an in bounds item so we will settle for an item in this order:
1994         // 1. bestSecondFind
1995         // 2. bestFirstFind
1996         // 3. invalidMatch
1997         return bestSecondFind != null ? bestSecondFind :
1998                 (bestFirstFind != null ? bestFirstFind : invalidMatch);
1999     }
2000 
2001     // returns the out-of-bound child view closest to RV's end bounds. An out-of-bound child is
2002     // defined as a child that's either partially or fully invisible (outside RV's padding area).
findPartiallyOrCompletelyInvisibleChildClosestToEnd()2003     private View findPartiallyOrCompletelyInvisibleChildClosestToEnd() {
2004         return mShouldReverseLayout ? findFirstPartiallyOrCompletelyInvisibleChild()
2005                 : findLastPartiallyOrCompletelyInvisibleChild();
2006     }
2007 
2008     // returns the out-of-bound child view closest to RV's starting bounds. An out-of-bound child is
2009     // defined as a child that's either partially or fully invisible (outside RV's padding area).
findPartiallyOrCompletelyInvisibleChildClosestToStart()2010     private View findPartiallyOrCompletelyInvisibleChildClosestToStart() {
2011         return mShouldReverseLayout ? findLastPartiallyOrCompletelyInvisibleChild() :
2012                 findFirstPartiallyOrCompletelyInvisibleChild();
2013     }
2014 
findFirstPartiallyOrCompletelyInvisibleChild()2015     private View findFirstPartiallyOrCompletelyInvisibleChild() {
2016         return findOnePartiallyOrCompletelyInvisibleChild(0, getChildCount());
2017     }
2018 
findLastPartiallyOrCompletelyInvisibleChild()2019     private View findLastPartiallyOrCompletelyInvisibleChild() {
2020         return findOnePartiallyOrCompletelyInvisibleChild(getChildCount() - 1, -1);
2021     }
2022 
2023     /**
2024      * Returns the adapter position of the first visible view. This position does not include
2025      * adapter changes that were dispatched after the last layout pass.
2026      * <p>
2027      * Note that, this value is not affected by layout orientation or item order traversal.
2028      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
2029      * not in the layout.
2030      * <p>
2031      * If RecyclerView has item decorators, they will be considered in calculations as well.
2032      * <p>
2033      * LayoutManager may pre-cache some views that are not necessarily visible. Those views
2034      * are ignored in this method.
2035      *
2036      * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
2037      * there aren't any visible items.
2038      * @see #findFirstCompletelyVisibleItemPosition()
2039      * @see #findLastVisibleItemPosition()
2040      */
findFirstVisibleItemPosition()2041     public int findFirstVisibleItemPosition() {
2042         final View child = findOneVisibleChild(0, getChildCount(), false, true);
2043         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
2044     }
2045 
2046     /**
2047      * Returns the adapter position of the first fully visible view. This position does not include
2048      * adapter changes that were dispatched after the last layout pass.
2049      * <p>
2050      * Note that bounds check is only performed in the current orientation. That means, if
2051      * LayoutManager is horizontal, it will only check the view's left and right edges.
2052      *
2053      * @return The adapter position of the first fully visible item or
2054      * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
2055      * @see #findFirstVisibleItemPosition()
2056      * @see #findLastCompletelyVisibleItemPosition()
2057      */
findFirstCompletelyVisibleItemPosition()2058     public int findFirstCompletelyVisibleItemPosition() {
2059         final View child = findOneVisibleChild(0, getChildCount(), true, false);
2060         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
2061     }
2062 
2063     /**
2064      * Returns the adapter position of the last visible view. This position does not include
2065      * adapter changes that were dispatched after the last layout pass.
2066      * <p>
2067      * Note that, this value is not affected by layout orientation or item order traversal.
2068      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
2069      * not in the layout.
2070      * <p>
2071      * If RecyclerView has item decorators, they will be considered in calculations as well.
2072      * <p>
2073      * LayoutManager may pre-cache some views that are not necessarily visible. Those views
2074      * are ignored in this method.
2075      *
2076      * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
2077      * there aren't any visible items.
2078      * @see #findLastCompletelyVisibleItemPosition()
2079      * @see #findFirstVisibleItemPosition()
2080      */
findLastVisibleItemPosition()2081     public int findLastVisibleItemPosition() {
2082         final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
2083         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
2084     }
2085 
2086     /**
2087      * Returns the adapter position of the last fully visible view. This position does not include
2088      * adapter changes that were dispatched after the last layout pass.
2089      * <p>
2090      * Note that bounds check is only performed in the current orientation. That means, if
2091      * LayoutManager is horizontal, it will only check the view's left and right edges.
2092      *
2093      * @return The adapter position of the last fully visible view or
2094      * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
2095      * @see #findLastVisibleItemPosition()
2096      * @see #findFirstCompletelyVisibleItemPosition()
2097      */
findLastCompletelyVisibleItemPosition()2098     public int findLastCompletelyVisibleItemPosition() {
2099         final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
2100         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
2101     }
2102 
2103     // Returns the first child that is visible in the provided index range, i.e. either partially or
2104     // fully visible depending on the arguments provided. Completely invisible children are not
2105     // acceptable by this method, but could be returned
2106     // using #findOnePartiallyOrCompletelyInvisibleChild
findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible)2107     View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
2108             boolean acceptPartiallyVisible) {
2109         ensureLayoutState();
2110         @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0;
2111         @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
2112         if (completelyVisible) {
2113             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS
2114                     | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE);
2115         } else {
2116             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
2117                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
2118         }
2119         if (acceptPartiallyVisible) {
2120             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
2121                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
2122         }
2123         return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck
2124                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
2125                         acceptableBoundsFlag) : mVerticalBoundCheck
2126                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
2127                         acceptableBoundsFlag);
2128     }
2129 
findOnePartiallyOrCompletelyInvisibleChild(int fromIndex, int toIndex)2130     View findOnePartiallyOrCompletelyInvisibleChild(int fromIndex, int toIndex) {
2131         ensureLayoutState();
2132         final int next = toIndex > fromIndex ? 1 : (toIndex < fromIndex ? -1 : 0);
2133         if (next == 0) {
2134             return getChildAt(fromIndex);
2135         }
2136         @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0;
2137         @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
2138         if (mOrientationHelper.getDecoratedStart(getChildAt(fromIndex))
2139                 < mOrientationHelper.getStartAfterPadding()) {
2140             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE
2141                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
2142             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS
2143                     | ViewBoundsCheck.FLAG_CVE_LT_PVE);
2144         } else {
2145             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE | ViewBoundsCheck.FLAG_CVS_GT_PVS
2146                     | ViewBoundsCheck.FLAG_CVS_LT_PVE);
2147             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE
2148                     | ViewBoundsCheck.FLAG_CVS_GT_PVS);
2149         }
2150         return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck
2151                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
2152                         acceptableBoundsFlag) : mVerticalBoundCheck
2153                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
2154                         acceptableBoundsFlag);
2155     }
2156 
2157     @Override
2158     @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state)2159     public View onFocusSearchFailed(View focused, int direction,
2160             RecyclerView.Recycler recycler, RecyclerView.State state) {
2161         resolveShouldLayoutReverse();
2162         if (getChildCount() == 0) {
2163             return null;
2164         }
2165 
2166         final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
2167         if (layoutDir == LayoutState.INVALID_LAYOUT) {
2168             return null;
2169         }
2170         ensureLayoutState();
2171         final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
2172         updateLayoutState(layoutDir, maxScroll, false, state);
2173         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
2174         mLayoutState.mRecycle = false;
2175         fill(recycler, mLayoutState, state, true);
2176 
2177         // nextCandidate is the first child view in the layout direction that's partially
2178         // within RV's bounds, i.e. part of it is visible or it's completely invisible but still
2179         // touching RV's bounds. This will be the unfocusable candidate view to become visible onto
2180         // the screen if no focusable views are found in the given layout direction.
2181         final View nextCandidate;
2182         if (layoutDir == LayoutState.LAYOUT_START) {
2183             nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToStart();
2184         } else {
2185             nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToEnd();
2186         }
2187         // nextFocus is meaningful only if it refers to a focusable child, in which case it
2188         // indicates the next view to gain focus.
2189         final View nextFocus;
2190         if (layoutDir == LayoutState.LAYOUT_START) {
2191             nextFocus = getChildClosestToStart();
2192         } else {
2193             nextFocus = getChildClosestToEnd();
2194         }
2195         if (nextFocus.hasFocusable()) {
2196             if (nextCandidate == null) {
2197                 return null;
2198             }
2199             return nextFocus;
2200         }
2201         return nextCandidate;
2202     }
2203 
2204     /**
2205      * Used for debugging.
2206      * Logs the internal representation of children to default logger.
2207      */
logChildren()2208     private void logChildren() {
2209         Log.d(TAG, "internal representation of views on the screen");
2210         for (int i = 0; i < getChildCount(); i++) {
2211             View child = getChildAt(i);
2212             Log.d(TAG, "item " + getPosition(child) + ", coord:"
2213                     + mOrientationHelper.getDecoratedStart(child));
2214         }
2215         Log.d(TAG, "==============");
2216     }
2217 
2218     /**
2219      * Used for debugging.
2220      * Validates that child views are laid out in correct order. This is important because rest of
2221      * the algorithm relies on this constraint.
2222      *
2223      * In default layout, child 0 should be closest to screen position 0 and last child should be
2224      * closest to position WIDTH or HEIGHT.
2225      * In reverse layout, last child should be closes to screen position 0 and first child should
2226      * be closest to position WIDTH  or HEIGHT
2227      */
validateChildOrder()2228     void validateChildOrder() {
2229         Log.d(TAG, "validating child count " + getChildCount());
2230         if (getChildCount() < 1) {
2231             return;
2232         }
2233         int lastPos = getPosition(getChildAt(0));
2234         int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
2235         if (mShouldReverseLayout) {
2236             for (int i = 1; i < getChildCount(); i++) {
2237                 View child = getChildAt(i);
2238                 int pos = getPosition(child);
2239                 int screenLoc = mOrientationHelper.getDecoratedStart(child);
2240                 if (pos < lastPos) {
2241                     logChildren();
2242                     throw new RuntimeException("detected invalid position. loc invalid? "
2243                             + (screenLoc < lastScreenLoc));
2244                 }
2245                 if (screenLoc > lastScreenLoc) {
2246                     logChildren();
2247                     throw new RuntimeException("detected invalid location");
2248                 }
2249             }
2250         } else {
2251             for (int i = 1; i < getChildCount(); i++) {
2252                 View child = getChildAt(i);
2253                 int pos = getPosition(child);
2254                 int screenLoc = mOrientationHelper.getDecoratedStart(child);
2255                 if (pos < lastPos) {
2256                     logChildren();
2257                     throw new RuntimeException("detected invalid position. loc invalid? "
2258                             + (screenLoc < lastScreenLoc));
2259                 }
2260                 if (screenLoc < lastScreenLoc) {
2261                     logChildren();
2262                     throw new RuntimeException("detected invalid location");
2263                 }
2264             }
2265         }
2266     }
2267 
2268     @Override
supportsPredictiveItemAnimations()2269     public boolean supportsPredictiveItemAnimations() {
2270         return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
2271     }
2272 
2273     /**
2274      * {@inheritDoc}
2275      */
2276     // This method is only intended to be called (and should only ever be called) by
2277     // ItemTouchHelper.
2278     @Override
prepareForDrop(@onNull View view, @NonNull View target, int x, int y)2279     public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) {
2280         assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
2281         ensureLayoutState();
2282         resolveShouldLayoutReverse();
2283         final int myPos = getPosition(view);
2284         final int targetPos = getPosition(target);
2285         final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL
2286                 : LayoutState.ITEM_DIRECTION_HEAD;
2287         if (mShouldReverseLayout) {
2288             if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
2289                 scrollToPositionWithOffset(targetPos,
2290                         mOrientationHelper.getEndAfterPadding()
2291                                 - (mOrientationHelper.getDecoratedStart(target)
2292                                 + mOrientationHelper.getDecoratedMeasurement(view)));
2293             } else {
2294                 scrollToPositionWithOffset(targetPos,
2295                         mOrientationHelper.getEndAfterPadding()
2296                                 - mOrientationHelper.getDecoratedEnd(target));
2297             }
2298         } else {
2299             if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
2300                 scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
2301             } else {
2302                 scrollToPositionWithOffset(targetPos,
2303                         mOrientationHelper.getDecoratedEnd(target)
2304                                 - mOrientationHelper.getDecoratedMeasurement(view));
2305             }
2306         }
2307     }
2308 
2309     /**
2310      * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
2311      * space.
2312      */
2313     static class LayoutState {
2314 
2315         static final String TAG = "LLM#LayoutState";
2316 
2317         static final int LAYOUT_START = -1;
2318 
2319         static final int LAYOUT_END = 1;
2320 
2321         static final int INVALID_LAYOUT = Integer.MIN_VALUE;
2322 
2323         static final int ITEM_DIRECTION_HEAD = -1;
2324 
2325         static final int ITEM_DIRECTION_TAIL = 1;
2326 
2327         static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;
2328 
2329         /**
2330          * We may not want to recycle children in some cases (e.g. layout)
2331          */
2332         boolean mRecycle = true;
2333 
2334         /**
2335          * Pixel offset where layout should start
2336          */
2337         int mOffset;
2338 
2339         /**
2340          * Number of pixels that we should fill, in the layout direction.
2341          */
2342         int mAvailable;
2343 
2344         /**
2345          * Current position on the adapter to get the next item.
2346          */
2347         int mCurrentPosition;
2348 
2349         /**
2350          * Defines the direction in which the data adapter is traversed.
2351          * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
2352          */
2353         int mItemDirection;
2354 
2355         /**
2356          * Defines the direction in which the layout is filled.
2357          * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
2358          */
2359         int mLayoutDirection;
2360 
2361         /**
2362          * Used when LayoutState is constructed in a scrolling state.
2363          * It should be set the amount of scrolling we can make without creating a new view.
2364          * Settings this is required for efficient view recycling.
2365          */
2366         int mScrollingOffset;
2367 
2368         /**
2369          * Used if you want to pre-layout items that are not yet visible.
2370          * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
2371          * {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
2372          */
2373         int mExtraFillSpace = 0;
2374 
2375         /**
2376          * Contains the {@link #calculateExtraLayoutSpace(RecyclerView.State, int[])}  extra layout
2377          * space} that should be excluded for recycling when cleaning up the tail of the list during
2378          * a smooth scroll.
2379          */
2380         int mNoRecycleSpace = 0;
2381 
2382         /**
2383          * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
2384          * is set to true, we skip removed views since they should not be laid out in post layout
2385          * step.
2386          */
2387         boolean mIsPreLayout = false;
2388 
2389         /**
2390          * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
2391          * amount.
2392          */
2393         int mLastScrollDelta;
2394 
2395         /**
2396          * When LLM needs to layout particular views, it sets this list in which case, LayoutState
2397          * will only return views from this list and return null if it cannot find an item.
2398          */
2399         List<RecyclerView.ViewHolder> mScrapList = null;
2400 
2401         /**
2402          * Used when there is no limit in how many views can be laid out.
2403          */
2404         boolean mInfinite;
2405 
2406         /**
2407          * @return true if there are more items in the data adapter
2408          */
2409         boolean hasMore(RecyclerView.State state) {
2410             return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
2411         }
2412 
2413         /**
2414          * Gets the view for the next element that we should layout.
2415          * Also updates current item index to the next item, based on {@link #mItemDirection}
2416          *
2417          * @return The next element that we should layout.
2418          */
2419         View next(RecyclerView.Recycler recycler) {
2420             if (mScrapList != null) {
2421                 return nextViewFromScrapList();
2422             }
2423             final View view = recycler.getViewForPosition(mCurrentPosition);
2424             mCurrentPosition += mItemDirection;
2425             return view;
2426         }
2427 
2428         /**
2429          * Returns the next item from the scrap list.
2430          * <p>
2431          * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
2432          *
2433          * @return View if an item in the current position or direction exists if not null.
2434          */
2435         private View nextViewFromScrapList() {
2436             final int size = mScrapList.size();
2437             for (int i = 0; i < size; i++) {
2438                 final View view = mScrapList.get(i).itemView;
2439                 final RecyclerView.LayoutParams lp =
2440                         (RecyclerView.LayoutParams) view.getLayoutParams();
2441                 if (lp.isItemRemoved()) {
2442                     continue;
2443                 }
2444                 if (mCurrentPosition == lp.getViewLayoutPosition()) {
2445                     assignPositionFromScrapList(view);
2446                     return view;
2447                 }
2448             }
2449             return null;
2450         }
2451 
2452         public void assignPositionFromScrapList() {
2453             assignPositionFromScrapList(null);
2454         }
2455 
2456         public void assignPositionFromScrapList(View ignore) {
2457             final View closest = nextViewInLimitedList(ignore);
2458             if (closest == null) {
2459                 mCurrentPosition = RecyclerView.NO_POSITION;
2460             } else {
2461                 mCurrentPosition = ((RecyclerView.LayoutParams) closest.getLayoutParams())
2462                         .getViewLayoutPosition();
2463             }
2464         }
2465 
2466         public View nextViewInLimitedList(View ignore) {
2467             int size = mScrapList.size();
2468             View closest = null;
2469             int closestDistance = Integer.MAX_VALUE;
2470             if (DEBUG && mIsPreLayout) {
2471                 throw new IllegalStateException("Scrap list cannot be used in pre layout");
2472             }
2473             for (int i = 0; i < size; i++) {
2474                 View view = mScrapList.get(i).itemView;
2475                 final RecyclerView.LayoutParams lp =
2476                         (RecyclerView.LayoutParams) view.getLayoutParams();
2477                 if (view == ignore || lp.isItemRemoved()) {
2478                     continue;
2479                 }
2480                 final int distance = (lp.getViewLayoutPosition() - mCurrentPosition)
2481                         * mItemDirection;
2482                 if (distance < 0) {
2483                     continue; // item is not in current direction
2484                 }
2485                 if (distance < closestDistance) {
2486                     closest = view;
2487                     closestDistance = distance;
2488                     if (distance == 0) {
2489                         break;
2490                     }
2491                 }
2492             }
2493             return closest;
2494         }
2495 
2496         void log() {
2497             Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:"
2498                     + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
2499         }
2500     }
2501 
2502     /**
2503      */
2504     @RestrictTo(LIBRARY)
2505     @SuppressLint("BanParcelableUsage")
2506     public static class SavedState implements Parcelable {
2507 
2508         int mAnchorPosition;
2509 
2510         int mAnchorOffset;
2511 
2512         boolean mAnchorLayoutFromEnd;
2513 
2514         public SavedState() {
2515 
2516         }
2517 
2518         SavedState(Parcel in) {
2519             mAnchorPosition = in.readInt();
2520             mAnchorOffset = in.readInt();
2521             mAnchorLayoutFromEnd = in.readInt() == 1;
2522         }
2523 
2524         @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
2525         public SavedState(SavedState other) {
2526             mAnchorPosition = other.mAnchorPosition;
2527             mAnchorOffset = other.mAnchorOffset;
2528             mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
2529         }
2530 
2531         boolean hasValidAnchor() {
2532             return mAnchorPosition >= 0;
2533         }
2534 
2535         void invalidateAnchor() {
2536             mAnchorPosition = RecyclerView.NO_POSITION;
2537         }
2538 
2539         @Override
2540         public int describeContents() {
2541             return 0;
2542         }
2543 
2544         @Override
2545         public void writeToParcel(Parcel dest, int flags) {
2546             dest.writeInt(mAnchorPosition);
2547             dest.writeInt(mAnchorOffset);
2548             dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
2549         }
2550 
2551         public static final Parcelable.Creator<SavedState> CREATOR =
2552                 new Parcelable.Creator<SavedState>() {
2553                     @Override
2554                     public SavedState createFromParcel(Parcel in) {
2555                         return new SavedState(in);
2556                     }
2557 
2558                     @Override
2559                     public SavedState[] newArray(int size) {
2560                         return new SavedState[size];
2561                     }
2562                 };
2563     }
2564 
2565     /**
2566      * Simple data class to keep Anchor information
2567      */
2568     static class AnchorInfo {
2569         OrientationHelper mOrientationHelper;
2570         int mPosition;
2571         int mCoordinate;
2572         boolean mLayoutFromEnd;
2573         boolean mValid;
2574 
2575         AnchorInfo() {
2576             reset();
2577         }
2578 
2579         void reset() {
2580             mPosition = RecyclerView.NO_POSITION;
2581             mCoordinate = INVALID_OFFSET;
2582             mLayoutFromEnd = false;
2583             mValid = false;
2584         }
2585 
2586         /**
2587          * assigns anchor coordinate from the RecyclerView's padding depending on current
2588          * layoutFromEnd value
2589          */
2590         void assignCoordinateFromPadding() {
2591             mCoordinate = mLayoutFromEnd
2592                     ? mOrientationHelper.getEndAfterPadding()
2593                     : mOrientationHelper.getStartAfterPadding();
2594         }
2595 
2596         @Override
2597         public String toString() {
2598             return "AnchorInfo{"
2599                     + "mPosition=" + mPosition
2600                     + ", mCoordinate=" + mCoordinate
2601                     + ", mLayoutFromEnd=" + mLayoutFromEnd
2602                     + ", mValid=" + mValid
2603                     + '}';
2604         }
2605 
2606         boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
2607             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
2608             return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
2609                     && lp.getViewLayoutPosition() < state.getItemCount();
2610         }
2611 
2612         public void assignFromViewAndKeepVisibleRect(View child, int position) {
2613             final int spaceChange = mOrientationHelper.getTotalSpaceChange();
2614             if (spaceChange >= 0) {
2615                 assignFromView(child, position);
2616                 return;
2617             }
2618             mPosition = position;
2619             if (mLayoutFromEnd) {
2620                 final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
2621                 final int childEnd = mOrientationHelper.getDecoratedEnd(child);
2622                 final int previousEndMargin = prevLayoutEnd - childEnd;
2623                 mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin;
2624                 // ensure we did not push child's top out of bounds because of this
2625                 if (previousEndMargin > 0) { // we have room to shift bottom if necessary
2626                     final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
2627                     final int estimatedChildStart = mCoordinate - childSize;
2628                     final int layoutStart = mOrientationHelper.getStartAfterPadding();
2629                     final int previousStartMargin = mOrientationHelper.getDecoratedStart(child)
2630                             - layoutStart;
2631                     final int startReference = layoutStart + Math.min(previousStartMargin, 0);
2632                     final int startMargin = estimatedChildStart - startReference;
2633                     if (startMargin < 0) {
2634                         // offset to make top visible but not too much
2635                         mCoordinate += Math.min(previousEndMargin, -startMargin);
2636                     }
2637                 }
2638             } else {
2639                 final int childStart = mOrientationHelper.getDecoratedStart(child);
2640                 final int startMargin = childStart - mOrientationHelper.getStartAfterPadding();
2641                 mCoordinate = childStart;
2642                 if (startMargin > 0) { // we have room to fix end as well
2643                     final int estimatedEnd = childStart
2644                             + mOrientationHelper.getDecoratedMeasurement(child);
2645                     final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding()
2646                             - spaceChange;
2647                     final int previousEndMargin = previousLayoutEnd
2648                             - mOrientationHelper.getDecoratedEnd(child);
2649                     final int endReference = mOrientationHelper.getEndAfterPadding()
2650                             - Math.min(0, previousEndMargin);
2651                     final int endMargin = endReference - estimatedEnd;
2652                     if (endMargin < 0) {
2653                         mCoordinate -= Math.min(startMargin, -endMargin);
2654                     }
2655                 }
2656             }
2657         }
2658 
2659         public void assignFromView(View child, int position) {
2660             if (mLayoutFromEnd) {
2661                 mCoordinate = mOrientationHelper.getDecoratedEnd(child)
2662                         + mOrientationHelper.getTotalSpaceChange();
2663             } else {
2664                 mCoordinate = mOrientationHelper.getDecoratedStart(child);
2665             }
2666 
2667             mPosition = position;
2668         }
2669     }
2670 
2671     protected static class LayoutChunkResult {
2672         public int mConsumed;
2673         public boolean mFinished;
2674         public boolean mIgnoreConsumed;
2675         public boolean mFocusable;
2676 
2677         void resetInternal() {
2678             mConsumed = 0;
2679             mFinished = false;
2680             mIgnoreConsumed = false;
2681             mFocusable = false;
2682         }
2683     }
2684 }
2685