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