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