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