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