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