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