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