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