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