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