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