1/*
2 * Copyright 2018 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 androidx.recyclerview.widget;
18
19import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
21
22import android.content.Context;
23import android.graphics.PointF;
24import android.graphics.Rect;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.util.AttributeSet;
28import android.util.Log;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.accessibility.AccessibilityEvent;
32
33import androidx.annotation.NonNull;
34import androidx.annotation.Nullable;
35import androidx.annotation.RestrictTo;
36import androidx.core.view.ViewCompat;
37import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.BitSet;
42import java.util.List;
43
44/**
45 * A LayoutManager that lays out children in a staggered grid formation.
46 * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
47 * <p>
48 * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
49 * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
50 * control this behavior via {@link #setGapStrategy(int)}.
51 */
52public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
53        RecyclerView.SmoothScroller.ScrollVectorProvider {
54
55    private static final String TAG = "StaggeredGridLManager";
56
57    static final boolean DEBUG = false;
58
59    public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
60
61    public static final int VERTICAL = RecyclerView.VERTICAL;
62
63    /**
64     * Does not do anything to hide gaps.
65     */
66    public static final int GAP_HANDLING_NONE = 0;
67
68    /**
69     * @deprecated No longer supported.
70     */
71    @SuppressWarnings("unused")
72    @Deprecated
73    public static final int GAP_HANDLING_LAZY = 1;
74
75    /**
76     * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
77     * check if there are gaps in the because of full span items. If it finds, it will re-layout
78     * and move items to correct positions with animations.
79     * <p>
80     * For example, if LayoutManager ends up with the following layout due to adapter changes:
81     * <pre>
82     * AAA
83     * _BC
84     * DDD
85     * </pre>
86     * <p>
87     * It will animate to the following state:
88     * <pre>
89     * AAA
90     * BC_
91     * DDD
92     * </pre>
93     */
94    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
95
96    static final int INVALID_OFFSET = Integer.MIN_VALUE;
97    /**
98     * While trying to find next view to focus, LayoutManager will not try to scroll more
99     * than this factor times the total space of the list. If layout is vertical, total space is the
100     * height minus padding, if layout is horizontal, total space is the width minus padding.
101     */
102    private static final float MAX_SCROLL_FACTOR = 1 / 3f;
103
104    /**
105     * Number of spans
106     */
107    private int mSpanCount = -1;
108
109    Span[] mSpans;
110
111    /**
112     * Primary orientation is the layout's orientation, secondary orientation is the orientation
113     * for spans. Having both makes code much cleaner for calculations.
114     */
115    @NonNull
116    OrientationHelper mPrimaryOrientation;
117    @NonNull
118    OrientationHelper mSecondaryOrientation;
119
120    private int mOrientation;
121
122    /**
123     * The width or height per span, depending on the orientation.
124     */
125    private int mSizePerSpan;
126
127    @NonNull
128    private final LayoutState mLayoutState;
129
130    boolean mReverseLayout = false;
131
132    /**
133     * Aggregated reverse layout value that takes RTL into account.
134     */
135    boolean mShouldReverseLayout = false;
136
137    /**
138     * Temporary variable used during fill method to check which spans needs to be filled.
139     */
140    private BitSet mRemainingSpans;
141
142    /**
143     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
144     * layout which will check this variable and re-layout accordingly.
145     */
146    int mPendingScrollPosition = RecyclerView.NO_POSITION;
147
148    /**
149     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
150     * called.
151     */
152    int mPendingScrollPositionOffset = INVALID_OFFSET;
153
154    /**
155     * Keeps the mapping between the adapter positions and spans. This is necessary to provide
156     * a consistent experience when user scrolls the list.
157     */
158    LazySpanLookup mLazySpanLookup = new LazySpanLookup();
159
160    /**
161     * how we handle gaps in UI.
162     */
163    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
164
165    /**
166     * Saved state needs this information to properly layout on restore.
167     */
168    private boolean mLastLayoutFromEnd;
169
170    /**
171     * Saved state and onLayout needs this information to re-layout properly
172     */
173    private boolean mLastLayoutRTL;
174
175    /**
176     * SavedState is not handled until a layout happens. This is where we keep it until next
177     * layout.
178     */
179    private SavedState mPendingSavedState;
180
181    /**
182     * Re-used measurement specs. updated by onLayout.
183     */
184    private int mFullSizeSpec;
185
186    /**
187     * Re-used rectangle to get child decor offsets.
188     */
189    private final Rect mTmpRect = new Rect();
190
191    /**
192     * Re-used anchor info.
193     */
194    private final AnchorInfo mAnchorInfo = new AnchorInfo();
195
196    /**
197     * If a full span item is invalid / or created in reverse direction; it may create gaps in
198     * the UI. While laying out, if such case is detected, we set this flag.
199     * <p>
200     * After scrolling stops, we check this flag and if it is set, re-layout.
201     */
202    private boolean mLaidOutInvalidFullSpan = false;
203
204    /**
205     * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
206     * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
207     */
208    private boolean mSmoothScrollbarEnabled = true;
209
210    /**
211     * Temporary array used (solely in {@link #collectAdjacentPrefetchPositions}) for stashing and
212     * sorting distances to views being prefetched.
213     */
214    private int[] mPrefetchDistances;
215
216    private final Runnable mCheckForGapsRunnable = new Runnable() {
217        @Override
218        public void run() {
219            checkForGaps();
220        }
221    };
222
223    /**
224     * Constructor used when layout manager is set in XML by RecyclerView attribute
225     * "layoutManager". Defaults to single column and vertical.
226     */
227    @SuppressWarnings("unused")
228    public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
229            int defStyleRes) {
230        Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
231        setOrientation(properties.orientation);
232        setSpanCount(properties.spanCount);
233        setReverseLayout(properties.reverseLayout);
234        mLayoutState = new LayoutState();
235        createOrientationHelpers();
236    }
237
238    /**
239     * Creates a StaggeredGridLayoutManager with given parameters.
240     *
241     * @param spanCount   If orientation is vertical, spanCount is number of columns. If
242     *                    orientation is horizontal, spanCount is number of rows.
243     * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
244     */
245    public StaggeredGridLayoutManager(int spanCount, int orientation) {
246        mOrientation = orientation;
247        setSpanCount(spanCount);
248        mLayoutState = new LayoutState();
249        createOrientationHelpers();
250    }
251
252    @Override
253    public boolean isAutoMeasureEnabled() {
254        return mGapStrategy != GAP_HANDLING_NONE;
255    }
256
257    private void createOrientationHelpers() {
258        mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
259        mSecondaryOrientation = OrientationHelper
260                .createOrientationHelper(this, 1 - mOrientation);
261    }
262
263    /**
264     * Checks for gaps in the UI that may be caused by adapter changes.
265     * <p>
266     * When a full span item is laid out in reverse direction, it sets a flag which we check when
267     * scroll is stopped (or re-layout happens) and re-layout after first valid item.
268     */
269    boolean checkForGaps() {
270        if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) {
271            return false;
272        }
273        final int minPos, maxPos;
274        if (mShouldReverseLayout) {
275            minPos = getLastChildPosition();
276            maxPos = getFirstChildPosition();
277        } else {
278            minPos = getFirstChildPosition();
279            maxPos = getLastChildPosition();
280        }
281        if (minPos == 0) {
282            View gapView = hasGapsToFix();
283            if (gapView != null) {
284                mLazySpanLookup.clear();
285                requestSimpleAnimationsInNextLayout();
286                requestLayout();
287                return true;
288            }
289        }
290        if (!mLaidOutInvalidFullSpan) {
291            return false;
292        }
293        int invalidGapDir = mShouldReverseLayout ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
294        final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
295                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
296        if (invalidFsi == null) {
297            mLaidOutInvalidFullSpan = false;
298            mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
299            return false;
300        }
301        final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
302                .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
303                        invalidGapDir * -1, true);
304        if (validFsi == null) {
305            mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
306        } else {
307            mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
308        }
309        requestSimpleAnimationsInNextLayout();
310        requestLayout();
311        return true;
312    }
313
314    @Override
315    public void onScrollStateChanged(int state) {
316        if (state == RecyclerView.SCROLL_STATE_IDLE) {
317            checkForGaps();
318        }
319    }
320
321    @Override
322    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
323        super.onDetachedFromWindow(view, recycler);
324
325        removeCallbacks(mCheckForGapsRunnable);
326        for (int i = 0; i < mSpanCount; i++) {
327            mSpans[i].clear();
328        }
329        // SGLM will require fresh layout call to recover state after detach
330        view.requestLayout();
331    }
332
333    /**
334     * Checks for gaps if we've reached to the top of the list.
335     * <p>
336     * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
337     */
338    View hasGapsToFix() {
339        int startChildIndex = 0;
340        int endChildIndex = getChildCount() - 1;
341        BitSet mSpansToCheck = new BitSet(mSpanCount);
342        mSpansToCheck.set(0, mSpanCount, true);
343
344        final int firstChildIndex, childLimit;
345        final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
346
347        if (mShouldReverseLayout) {
348            firstChildIndex = endChildIndex;
349            childLimit = startChildIndex - 1;
350        } else {
351            firstChildIndex = startChildIndex;
352            childLimit = endChildIndex + 1;
353        }
354        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
355        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
356            View child = getChildAt(i);
357            LayoutParams lp = (LayoutParams) child.getLayoutParams();
358            if (mSpansToCheck.get(lp.mSpan.mIndex)) {
359                if (checkSpanForGap(lp.mSpan)) {
360                    return child;
361                }
362                mSpansToCheck.clear(lp.mSpan.mIndex);
363            }
364            if (lp.mFullSpan) {
365                continue; // quick reject
366            }
367
368            if (i + nextChildDiff != childLimit) {
369                View nextChild = getChildAt(i + nextChildDiff);
370                boolean compareSpans = false;
371                if (mShouldReverseLayout) {
372                    // ensure child's end is below nextChild's end
373                    int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
374                    int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
375                    if (myEnd < nextEnd) {
376                        return child; //i should have a better position
377                    } else if (myEnd == nextEnd) {
378                        compareSpans = true;
379                    }
380                } else {
381                    int myStart = mPrimaryOrientation.getDecoratedStart(child);
382                    int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
383                    if (myStart > nextStart) {
384                        return child; //i should have a better position
385                    } else if (myStart == nextStart) {
386                        compareSpans = true;
387                    }
388                }
389                if (compareSpans) {
390                    // equal, check span indices.
391                    LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
392                    if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
393                        return child;
394                    }
395                }
396            }
397        }
398        // everything looks good
399        return null;
400    }
401
402    private boolean checkSpanForGap(Span span) {
403        if (mShouldReverseLayout) {
404            if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
405                // if it is full span, it is OK
406                final View endView = span.mViews.get(span.mViews.size() - 1);
407                final LayoutParams lp = span.getLayoutParams(endView);
408                return !lp.mFullSpan;
409            }
410        } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
411            // if it is full span, it is OK
412            final View startView = span.mViews.get(0);
413            final LayoutParams lp = span.getLayoutParams(startView);
414            return !lp.mFullSpan;
415        }
416        return false;
417    }
418
419    /**
420     * Sets the number of spans for the layout. This will invalidate all of the span assignments
421     * for Views.
422     * <p>
423     * Calling this method will automatically result in a new layout request unless the spanCount
424     * parameter is equal to current span count.
425     *
426     * @param spanCount Number of spans to layout
427     */
428    public void setSpanCount(int spanCount) {
429        assertNotInLayoutOrScroll(null);
430        if (spanCount != mSpanCount) {
431            invalidateSpanAssignments();
432            mSpanCount = spanCount;
433            mRemainingSpans = new BitSet(mSpanCount);
434            mSpans = new Span[mSpanCount];
435            for (int i = 0; i < mSpanCount; i++) {
436                mSpans[i] = new Span(i);
437            }
438            requestLayout();
439        }
440    }
441
442    /**
443     * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
444     * scroll position if this method is called after views are laid out.
445     *
446     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
447     */
448    public void setOrientation(int orientation) {
449        if (orientation != HORIZONTAL && orientation != VERTICAL) {
450            throw new IllegalArgumentException("invalid orientation.");
451        }
452        assertNotInLayoutOrScroll(null);
453        if (orientation == mOrientation) {
454            return;
455        }
456        mOrientation = orientation;
457        OrientationHelper tmp = mPrimaryOrientation;
458        mPrimaryOrientation = mSecondaryOrientation;
459        mSecondaryOrientation = tmp;
460        requestLayout();
461    }
462
463    /**
464     * Sets whether LayoutManager should start laying out items from the end of the UI. The order
465     * items are traversed is not affected by this call.
466     * <p>
467     * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
468     * the list.
469     * <p>
470     * For horizontal layouts, it depends on the layout direction.
471     * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
472     * {@link RecyclerView}} is RTL, it will layout from LTR.
473     *
474     * @param reverseLayout Whether layout should be in reverse or not
475     */
476    public void setReverseLayout(boolean reverseLayout) {
477        assertNotInLayoutOrScroll(null);
478        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
479            mPendingSavedState.mReverseLayout = reverseLayout;
480        }
481        mReverseLayout = reverseLayout;
482        requestLayout();
483    }
484
485    /**
486     * Returns the current gap handling strategy for StaggeredGridLayoutManager.
487     * <p>
488     * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
489     * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
490     * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
491     * <p>
492     * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
493     *
494     * @return Current gap handling strategy.
495     * @see #setGapStrategy(int)
496     * @see #GAP_HANDLING_NONE
497     * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
498     */
499    public int getGapStrategy() {
500        return mGapStrategy;
501    }
502
503    /**
504     * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
505     * is different than the current strategy, calling this method will trigger a layout request.
506     *
507     * @param gapStrategy The new gap handling strategy. Should be
508     *                    {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
509     *                    #GAP_HANDLING_NONE}.
510     * @see #getGapStrategy()
511     */
512    public void setGapStrategy(int gapStrategy) {
513        assertNotInLayoutOrScroll(null);
514        if (gapStrategy == mGapStrategy) {
515            return;
516        }
517        if (gapStrategy != GAP_HANDLING_NONE
518                && gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
519            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
520                    + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
521        }
522        mGapStrategy = gapStrategy;
523        requestLayout();
524    }
525
526    @Override
527    public void assertNotInLayoutOrScroll(String message) {
528        if (mPendingSavedState == null) {
529            super.assertNotInLayoutOrScroll(message);
530        }
531    }
532
533    /**
534     * Returns the number of spans laid out by StaggeredGridLayoutManager.
535     *
536     * @return Number of spans in the layout
537     */
538    public int getSpanCount() {
539        return mSpanCount;
540    }
541
542    /**
543     * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
544     * <p>
545     * If you need to cancel current assignments, you can call this method which will clear all
546     * assignments and request a new layout.
547     */
548    public void invalidateSpanAssignments() {
549        mLazySpanLookup.clear();
550        requestLayout();
551    }
552
553    /**
554     * Calculates the views' layout order. (e.g. from end to start or start to end)
555     * RTL layout support is applied automatically. So if layout is RTL and
556     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
557     */
558    private void resolveShouldLayoutReverse() {
559        // A == B is the same result, but we rather keep it readable
560        if (mOrientation == VERTICAL || !isLayoutRTL()) {
561            mShouldReverseLayout = mReverseLayout;
562        } else {
563            mShouldReverseLayout = !mReverseLayout;
564        }
565    }
566
567    boolean isLayoutRTL() {
568        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
569    }
570
571    /**
572     * Returns whether views are laid out in reverse order or not.
573     * <p>
574     * Not that this value is not affected by RecyclerView's layout direction.
575     *
576     * @return True if layout is reversed, false otherwise
577     * @see #setReverseLayout(boolean)
578     */
579    public boolean getReverseLayout() {
580        return mReverseLayout;
581    }
582
583    @Override
584    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
585        // we don't like it to wrap content in our non-scroll direction.
586        final int width, height;
587        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
588        final int verticalPadding = getPaddingTop() + getPaddingBottom();
589        if (mOrientation == VERTICAL) {
590            final int usedHeight = childrenBounds.height() + verticalPadding;
591            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
592            width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding,
593                    getMinimumWidth());
594        } else {
595            final int usedWidth = childrenBounds.width() + horizontalPadding;
596            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
597            height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding,
598                    getMinimumHeight());
599        }
600        setMeasuredDimension(width, height);
601    }
602
603    @Override
604    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
605        onLayoutChildren(recycler, state, true);
606    }
607
608
609    private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state,
610            boolean shouldCheckForGaps) {
611        final AnchorInfo anchorInfo = mAnchorInfo;
612        if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
613            if (state.getItemCount() == 0) {
614                removeAndRecycleAllViews(recycler);
615                anchorInfo.reset();
616                return;
617            }
618        }
619
620        boolean recalculateAnchor = !anchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
621                || mPendingSavedState != null;
622        if (recalculateAnchor) {
623            anchorInfo.reset();
624            if (mPendingSavedState != null) {
625                applyPendingSavedState(anchorInfo);
626            } else {
627                resolveShouldLayoutReverse();
628                anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
629            }
630            updateAnchorInfoForLayout(state, anchorInfo);
631            anchorInfo.mValid = true;
632        }
633        if (mPendingSavedState == null && mPendingScrollPosition == RecyclerView.NO_POSITION) {
634            if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd
635                    || isLayoutRTL() != mLastLayoutRTL) {
636                mLazySpanLookup.clear();
637                anchorInfo.mInvalidateOffsets = true;
638            }
639        }
640
641        if (getChildCount() > 0 && (mPendingSavedState == null
642                || mPendingSavedState.mSpanOffsetsSize < 1)) {
643            if (anchorInfo.mInvalidateOffsets) {
644                for (int i = 0; i < mSpanCount; i++) {
645                    // Scroll to position is set, clear.
646                    mSpans[i].clear();
647                    if (anchorInfo.mOffset != INVALID_OFFSET) {
648                        mSpans[i].setLine(anchorInfo.mOffset);
649                    }
650                }
651            } else {
652                if (recalculateAnchor || mAnchorInfo.mSpanReferenceLines == null) {
653                    for (int i = 0; i < mSpanCount; i++) {
654                        mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout,
655                                anchorInfo.mOffset);
656                    }
657                    mAnchorInfo.saveSpanReferenceLines(mSpans);
658                } else {
659                    for (int i = 0; i < mSpanCount; i++) {
660                        final Span span = mSpans[i];
661                        span.clear();
662                        span.setLine(mAnchorInfo.mSpanReferenceLines[i]);
663                    }
664                }
665            }
666        }
667        detachAndScrapAttachedViews(recycler);
668        mLayoutState.mRecycle = false;
669        mLaidOutInvalidFullSpan = false;
670        updateMeasureSpecs(mSecondaryOrientation.getTotalSpace());
671        updateLayoutState(anchorInfo.mPosition, state);
672        if (anchorInfo.mLayoutFromEnd) {
673            // Layout start.
674            setLayoutStateDirection(LayoutState.LAYOUT_START);
675            fill(recycler, mLayoutState, state);
676            // Layout end.
677            setLayoutStateDirection(LayoutState.LAYOUT_END);
678            mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
679            fill(recycler, mLayoutState, state);
680        } else {
681            // Layout end.
682            setLayoutStateDirection(LayoutState.LAYOUT_END);
683            fill(recycler, mLayoutState, state);
684            // Layout start.
685            setLayoutStateDirection(LayoutState.LAYOUT_START);
686            mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
687            fill(recycler, mLayoutState, state);
688        }
689
690        repositionToWrapContentIfNecessary();
691
692        if (getChildCount() > 0) {
693            if (mShouldReverseLayout) {
694                fixEndGap(recycler, state, true);
695                fixStartGap(recycler, state, false);
696            } else {
697                fixStartGap(recycler, state, true);
698                fixEndGap(recycler, state, false);
699            }
700        }
701        boolean hasGaps = false;
702        if (shouldCheckForGaps && !state.isPreLayout()) {
703            final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
704                    && getChildCount() > 0
705                    && (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
706            if (needToCheckForGaps) {
707                removeCallbacks(mCheckForGapsRunnable);
708                if (checkForGaps()) {
709                    hasGaps = true;
710                }
711            }
712        }
713        if (state.isPreLayout()) {
714            mAnchorInfo.reset();
715        }
716        mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
717        mLastLayoutRTL = isLayoutRTL();
718        if (hasGaps) {
719            mAnchorInfo.reset();
720            onLayoutChildren(recycler, state, false);
721        }
722    }
723
724    @Override
725    public void onLayoutCompleted(RecyclerView.State state) {
726        super.onLayoutCompleted(state);
727        mPendingScrollPosition = RecyclerView.NO_POSITION;
728        mPendingScrollPositionOffset = INVALID_OFFSET;
729        mPendingSavedState = null; // we don't need this anymore
730        mAnchorInfo.reset();
731    }
732
733    private void repositionToWrapContentIfNecessary() {
734        if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
735            return; // nothing to do
736        }
737        float maxSize = 0;
738        final int childCount = getChildCount();
739        for (int i = 0; i < childCount; i++) {
740            View child = getChildAt(i);
741            float size = mSecondaryOrientation.getDecoratedMeasurement(child);
742            if (size < maxSize) {
743                continue;
744            }
745            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
746            if (layoutParams.isFullSpan()) {
747                size = 1f * size / mSpanCount;
748            }
749            maxSize = Math.max(maxSize, size);
750        }
751        int before = mSizePerSpan;
752        int desired = Math.round(maxSize * mSpanCount);
753        if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) {
754            desired = Math.min(desired, mSecondaryOrientation.getTotalSpace());
755        }
756        updateMeasureSpecs(desired);
757        if (mSizePerSpan == before) {
758            return; // nothing has changed
759        }
760        for (int i = 0; i < childCount; i++) {
761            View child = getChildAt(i);
762            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
763            if (lp.mFullSpan) {
764                continue;
765            }
766            if (isLayoutRTL() && mOrientation == VERTICAL) {
767                int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan;
768                int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before;
769                child.offsetLeftAndRight(newOffset - prevOffset);
770            } else {
771                int newOffset = lp.mSpan.mIndex * mSizePerSpan;
772                int prevOffset = lp.mSpan.mIndex * before;
773                if (mOrientation == VERTICAL) {
774                    child.offsetLeftAndRight(newOffset - prevOffset);
775                } else {
776                    child.offsetTopAndBottom(newOffset - prevOffset);
777                }
778            }
779        }
780    }
781
782    private void applyPendingSavedState(AnchorInfo anchorInfo) {
783        if (DEBUG) {
784            Log.d(TAG, "found saved state: " + mPendingSavedState);
785        }
786        if (mPendingSavedState.mSpanOffsetsSize > 0) {
787            if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
788                for (int i = 0; i < mSpanCount; i++) {
789                    mSpans[i].clear();
790                    int line = mPendingSavedState.mSpanOffsets[i];
791                    if (line != Span.INVALID_LINE) {
792                        if (mPendingSavedState.mAnchorLayoutFromEnd) {
793                            line += mPrimaryOrientation.getEndAfterPadding();
794                        } else {
795                            line += mPrimaryOrientation.getStartAfterPadding();
796                        }
797                    }
798                    mSpans[i].setLine(line);
799                }
800            } else {
801                mPendingSavedState.invalidateSpanInfo();
802                mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
803            }
804        }
805        mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
806        setReverseLayout(mPendingSavedState.mReverseLayout);
807        resolveShouldLayoutReverse();
808
809        if (mPendingSavedState.mAnchorPosition != RecyclerView.NO_POSITION) {
810            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
811            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
812        } else {
813            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
814        }
815        if (mPendingSavedState.mSpanLookupSize > 1) {
816            mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
817            mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
818        }
819    }
820
821    void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
822        if (updateAnchorFromPendingData(state, anchorInfo)) {
823            return;
824        }
825        if (updateAnchorFromChildren(state, anchorInfo)) {
826            return;
827        }
828        if (DEBUG) {
829            Log.d(TAG, "Deciding anchor info from fresh state");
830        }
831        anchorInfo.assignCoordinateFromPadding();
832        anchorInfo.mPosition = 0;
833    }
834
835    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
836        // We don't recycle views out of adapter order. This way, we can rely on the first or
837        // last child as the anchor position.
838        // Layout direction may change but we should select the child depending on the latest
839        // layout direction. Otherwise, we'll choose the wrong child.
840        anchorInfo.mPosition = mLastLayoutFromEnd
841                ? findLastReferenceChildPosition(state.getItemCount())
842                : findFirstReferenceChildPosition(state.getItemCount());
843        anchorInfo.mOffset = INVALID_OFFSET;
844        return true;
845    }
846
847    boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
848        // Validate scroll position if exists.
849        if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
850            return false;
851        }
852        // Validate it.
853        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
854            mPendingScrollPosition = RecyclerView.NO_POSITION;
855            mPendingScrollPositionOffset = INVALID_OFFSET;
856            return false;
857        }
858
859        if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == RecyclerView.NO_POSITION
860                || mPendingSavedState.mSpanOffsetsSize < 1) {
861            // If item is visible, make it fully visible.
862            final View child = findViewByPosition(mPendingScrollPosition);
863            if (child != null) {
864                // Use regular anchor position, offset according to pending offset and target
865                // child
866                anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
867                        : getFirstChildPosition();
868                if (mPendingScrollPositionOffset != INVALID_OFFSET) {
869                    if (anchorInfo.mLayoutFromEnd) {
870                        final int target = mPrimaryOrientation.getEndAfterPadding()
871                                - mPendingScrollPositionOffset;
872                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
873                    } else {
874                        final int target = mPrimaryOrientation.getStartAfterPadding()
875                                + mPendingScrollPositionOffset;
876                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
877                    }
878                    return true;
879                }
880
881                // no offset provided. Decide according to the child location
882                final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
883                if (childSize > mPrimaryOrientation.getTotalSpace()) {
884                    // Item does not fit. Fix depending on layout direction.
885                    anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
886                            ? mPrimaryOrientation.getEndAfterPadding()
887                            : mPrimaryOrientation.getStartAfterPadding();
888                    return true;
889                }
890
891                final int startGap = mPrimaryOrientation.getDecoratedStart(child)
892                        - mPrimaryOrientation.getStartAfterPadding();
893                if (startGap < 0) {
894                    anchorInfo.mOffset = -startGap;
895                    return true;
896                }
897                final int endGap = mPrimaryOrientation.getEndAfterPadding()
898                        - mPrimaryOrientation.getDecoratedEnd(child);
899                if (endGap < 0) {
900                    anchorInfo.mOffset = endGap;
901                    return true;
902                }
903                // child already visible. just layout as usual
904                anchorInfo.mOffset = INVALID_OFFSET;
905            } else {
906                // Child is not visible. Set anchor coordinate depending on in which direction
907                // child will be visible.
908                anchorInfo.mPosition = mPendingScrollPosition;
909                if (mPendingScrollPositionOffset == INVALID_OFFSET) {
910                    final int position = calculateScrollDirectionForPosition(
911                            anchorInfo.mPosition);
912                    anchorInfo.mLayoutFromEnd = position == LayoutState.LAYOUT_END;
913                    anchorInfo.assignCoordinateFromPadding();
914                } else {
915                    anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
916                }
917                anchorInfo.mInvalidateOffsets = true;
918            }
919        } else {
920            anchorInfo.mOffset = INVALID_OFFSET;
921            anchorInfo.mPosition = mPendingScrollPosition;
922        }
923        return true;
924    }
925
926    void updateMeasureSpecs(int totalSpace) {
927        mSizePerSpan = totalSpace / mSpanCount;
928        //noinspection ResourceType
929        mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
930                totalSpace, mSecondaryOrientation.getMode());
931    }
932
933    @Override
934    public boolean supportsPredictiveItemAnimations() {
935        return mPendingSavedState == null;
936    }
937
938    /**
939     * Returns the adapter position of the first visible view for each span.
940     * <p>
941     * Note that, this value is not affected by layout orientation or item order traversal.
942     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
943     * not in the layout.
944     * <p>
945     * If RecyclerView has item decorators, they will be considered in calculations as well.
946     * <p>
947     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
948     * views are ignored in this method.
949     *
950     * @param into An array to put the results into. If you don't provide any, LayoutManager will
951     *             create a new one.
952     * @return The adapter position of the first visible item in each span. If a span does not have
953     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
954     * @see #findFirstCompletelyVisibleItemPositions(int[])
955     * @see #findLastVisibleItemPositions(int[])
956     */
957    public int[] findFirstVisibleItemPositions(int[] into) {
958        if (into == null) {
959            into = new int[mSpanCount];
960        } else if (into.length < mSpanCount) {
961            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
962                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
963        }
964        for (int i = 0; i < mSpanCount; i++) {
965            into[i] = mSpans[i].findFirstVisibleItemPosition();
966        }
967        return into;
968    }
969
970    /**
971     * Returns the adapter position of the first completely visible view for each span.
972     * <p>
973     * Note that, this value is not affected by layout orientation or item order traversal.
974     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
975     * not in the layout.
976     * <p>
977     * If RecyclerView has item decorators, they will be considered in calculations as well.
978     * <p>
979     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
980     * views are ignored in this method.
981     *
982     * @param into An array to put the results into. If you don't provide any, LayoutManager will
983     *             create a new one.
984     * @return The adapter position of the first fully visible item in each span. If a span does
985     * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
986     * @see #findFirstVisibleItemPositions(int[])
987     * @see #findLastCompletelyVisibleItemPositions(int[])
988     */
989    public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
990        if (into == null) {
991            into = new int[mSpanCount];
992        } else if (into.length < mSpanCount) {
993            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
994                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
995        }
996        for (int i = 0; i < mSpanCount; i++) {
997            into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
998        }
999        return into;
1000    }
1001
1002    /**
1003     * Returns the adapter position of the last visible view for each span.
1004     * <p>
1005     * Note that, this value is not affected by layout orientation or item order traversal.
1006     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
1007     * not in the layout.
1008     * <p>
1009     * If RecyclerView has item decorators, they will be considered in calculations as well.
1010     * <p>
1011     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
1012     * views are ignored in this method.
1013     *
1014     * @param into An array to put the results into. If you don't provide any, LayoutManager will
1015     *             create a new one.
1016     * @return The adapter position of the last visible item in each span. If a span does not have
1017     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
1018     * @see #findLastCompletelyVisibleItemPositions(int[])
1019     * @see #findFirstVisibleItemPositions(int[])
1020     */
1021    public int[] findLastVisibleItemPositions(int[] into) {
1022        if (into == null) {
1023            into = new int[mSpanCount];
1024        } else if (into.length < mSpanCount) {
1025            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
1026                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
1027        }
1028        for (int i = 0; i < mSpanCount; i++) {
1029            into[i] = mSpans[i].findLastVisibleItemPosition();
1030        }
1031        return into;
1032    }
1033
1034    /**
1035     * Returns the adapter position of the last completely visible view for each span.
1036     * <p>
1037     * Note that, this value is not affected by layout orientation or item order traversal.
1038     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
1039     * not in the layout.
1040     * <p>
1041     * If RecyclerView has item decorators, they will be considered in calculations as well.
1042     * <p>
1043     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
1044     * views are ignored in this method.
1045     *
1046     * @param into An array to put the results into. If you don't provide any, LayoutManager will
1047     *             create a new one.
1048     * @return The adapter position of the last fully visible item in each span. If a span does not
1049     * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
1050     * @see #findFirstCompletelyVisibleItemPositions(int[])
1051     * @see #findLastVisibleItemPositions(int[])
1052     */
1053    public int[] findLastCompletelyVisibleItemPositions(int[] into) {
1054        if (into == null) {
1055            into = new int[mSpanCount];
1056        } else if (into.length < mSpanCount) {
1057            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
1058                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
1059        }
1060        for (int i = 0; i < mSpanCount; i++) {
1061            into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
1062        }
1063        return into;
1064    }
1065
1066    @Override
1067    public int computeHorizontalScrollOffset(RecyclerView.State state) {
1068        return computeScrollOffset(state);
1069    }
1070
1071    private int computeScrollOffset(RecyclerView.State state) {
1072        if (getChildCount() == 0) {
1073            return 0;
1074        }
1075        return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
1076                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
1077                findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
1078                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
1079    }
1080
1081    @Override
1082    public int computeVerticalScrollOffset(RecyclerView.State state) {
1083        return computeScrollOffset(state);
1084    }
1085
1086    @Override
1087    public int computeHorizontalScrollExtent(RecyclerView.State state) {
1088        return computeScrollExtent(state);
1089    }
1090
1091    private int computeScrollExtent(RecyclerView.State state) {
1092        if (getChildCount() == 0) {
1093            return 0;
1094        }
1095        return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
1096                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
1097                findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
1098                this, mSmoothScrollbarEnabled);
1099    }
1100
1101    @Override
1102    public int computeVerticalScrollExtent(RecyclerView.State state) {
1103        return computeScrollExtent(state);
1104    }
1105
1106    @Override
1107    public int computeHorizontalScrollRange(RecyclerView.State state) {
1108        return computeScrollRange(state);
1109    }
1110
1111    private int computeScrollRange(RecyclerView.State state) {
1112        if (getChildCount() == 0) {
1113            return 0;
1114        }
1115        return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
1116                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
1117                findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
1118                this, mSmoothScrollbarEnabled);
1119    }
1120
1121    @Override
1122    public int computeVerticalScrollRange(RecyclerView.State state) {
1123        return computeScrollRange(state);
1124    }
1125
1126    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp,
1127            boolean alreadyMeasured) {
1128        if (lp.mFullSpan) {
1129            if (mOrientation == VERTICAL) {
1130                measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
1131                        getChildMeasureSpec(
1132                                getHeight(),
1133                                getHeightMode(),
1134                                getPaddingTop() + getPaddingBottom(),
1135                                lp.height,
1136                                true),
1137                        alreadyMeasured);
1138            } else {
1139                measureChildWithDecorationsAndMargin(
1140                        child,
1141                        getChildMeasureSpec(
1142                                getWidth(),
1143                                getWidthMode(),
1144                                getPaddingLeft() + getPaddingRight(),
1145                                lp.width,
1146                                true),
1147                        mFullSizeSpec,
1148                        alreadyMeasured);
1149            }
1150        } else {
1151            if (mOrientation == VERTICAL) {
1152                // Padding for width measure spec is 0 because left and right padding were already
1153                // factored into mSizePerSpan.
1154                measureChildWithDecorationsAndMargin(
1155                        child,
1156                        getChildMeasureSpec(
1157                                mSizePerSpan,
1158                                getWidthMode(),
1159                                0,
1160                                lp.width,
1161                                false),
1162                        getChildMeasureSpec(
1163                                getHeight(),
1164                                getHeightMode(),
1165                                getPaddingTop() + getPaddingBottom(),
1166                                lp.height,
1167                                true),
1168                        alreadyMeasured);
1169            } else {
1170                // Padding for height measure spec is 0 because top and bottom padding were already
1171                // factored into mSizePerSpan.
1172                measureChildWithDecorationsAndMargin(
1173                        child,
1174                        getChildMeasureSpec(
1175                                getWidth(),
1176                                getWidthMode(),
1177                                getPaddingLeft() + getPaddingRight(),
1178                                lp.width,
1179                                true),
1180                        getChildMeasureSpec(
1181                                mSizePerSpan,
1182                                getHeightMode(),
1183                                0,
1184                                lp.height,
1185                                false),
1186                        alreadyMeasured);
1187            }
1188        }
1189    }
1190
1191    private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
1192            int heightSpec, boolean alreadyMeasured) {
1193        calculateItemDecorationsForChild(child, mTmpRect);
1194        LayoutParams lp = (LayoutParams) child.getLayoutParams();
1195        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
1196                lp.rightMargin + mTmpRect.right);
1197        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
1198                lp.bottomMargin + mTmpRect.bottom);
1199        final boolean measure = alreadyMeasured
1200                ? shouldReMeasureChild(child, widthSpec, heightSpec, lp)
1201                : shouldMeasureChild(child, widthSpec, heightSpec, lp);
1202        if (measure) {
1203            child.measure(widthSpec, heightSpec);
1204        }
1205
1206    }
1207
1208    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
1209        if (startInset == 0 && endInset == 0) {
1210            return spec;
1211        }
1212        final int mode = View.MeasureSpec.getMode(spec);
1213        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
1214            return View.MeasureSpec.makeMeasureSpec(
1215                    Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode);
1216        }
1217        return spec;
1218    }
1219
1220    @Override
1221    public void onRestoreInstanceState(Parcelable state) {
1222        if (state instanceof SavedState) {
1223            mPendingSavedState = (SavedState) state;
1224            requestLayout();
1225        } else if (DEBUG) {
1226            Log.d(TAG, "invalid saved state class");
1227        }
1228    }
1229
1230    @Override
1231    public Parcelable onSaveInstanceState() {
1232        if (mPendingSavedState != null) {
1233            return new SavedState(mPendingSavedState);
1234        }
1235        SavedState state = new SavedState();
1236        state.mReverseLayout = mReverseLayout;
1237        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
1238        state.mLastLayoutRTL = mLastLayoutRTL;
1239
1240        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
1241            state.mSpanLookup = mLazySpanLookup.mData;
1242            state.mSpanLookupSize = state.mSpanLookup.length;
1243            state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
1244        } else {
1245            state.mSpanLookupSize = 0;
1246        }
1247
1248        if (getChildCount() > 0) {
1249            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
1250                    : getFirstChildPosition();
1251            state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
1252            state.mSpanOffsetsSize = mSpanCount;
1253            state.mSpanOffsets = new int[mSpanCount];
1254            for (int i = 0; i < mSpanCount; i++) {
1255                int line;
1256                if (mLastLayoutFromEnd) {
1257                    line = mSpans[i].getEndLine(Span.INVALID_LINE);
1258                    if (line != Span.INVALID_LINE) {
1259                        line -= mPrimaryOrientation.getEndAfterPadding();
1260                    }
1261                } else {
1262                    line = mSpans[i].getStartLine(Span.INVALID_LINE);
1263                    if (line != Span.INVALID_LINE) {
1264                        line -= mPrimaryOrientation.getStartAfterPadding();
1265                    }
1266                }
1267                state.mSpanOffsets[i] = line;
1268            }
1269        } else {
1270            state.mAnchorPosition = RecyclerView.NO_POSITION;
1271            state.mVisibleAnchorPosition = RecyclerView.NO_POSITION;
1272            state.mSpanOffsetsSize = 0;
1273        }
1274        if (DEBUG) {
1275            Log.d(TAG, "saved state:\n" + state);
1276        }
1277        return state;
1278    }
1279
1280    @Override
1281    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
1282            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
1283        ViewGroup.LayoutParams lp = host.getLayoutParams();
1284        if (!(lp instanceof LayoutParams)) {
1285            super.onInitializeAccessibilityNodeInfoForItem(host, info);
1286            return;
1287        }
1288        LayoutParams sglp = (LayoutParams) lp;
1289        if (mOrientation == HORIZONTAL) {
1290            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1291                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1292                    -1, -1,
1293                    sglp.mFullSpan, false));
1294        } else { // VERTICAL
1295            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1296                    -1, -1,
1297                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1298                    sglp.mFullSpan, false));
1299        }
1300    }
1301
1302    @Override
1303    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1304        super.onInitializeAccessibilityEvent(event);
1305        if (getChildCount() > 0) {
1306            final View start = findFirstVisibleItemClosestToStart(false);
1307            final View end = findFirstVisibleItemClosestToEnd(false);
1308            if (start == null || end == null) {
1309                return;
1310            }
1311            final int startPos = getPosition(start);
1312            final int endPos = getPosition(end);
1313            if (startPos < endPos) {
1314                event.setFromIndex(startPos);
1315                event.setToIndex(endPos);
1316            } else {
1317                event.setFromIndex(endPos);
1318                event.setToIndex(startPos);
1319            }
1320        }
1321    }
1322
1323    /**
1324     * Finds the first fully visible child to be used as an anchor child if span count changes when
1325     * state is restored. If no children is fully visible, returns a partially visible child instead
1326     * of returning null.
1327     */
1328    int findFirstVisibleItemPositionInt() {
1329        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
1330                findFirstVisibleItemClosestToStart(true);
1331        return first == null ? RecyclerView.NO_POSITION : getPosition(first);
1332    }
1333
1334    @Override
1335    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
1336            RecyclerView.State state) {
1337        if (mOrientation == HORIZONTAL) {
1338            return mSpanCount;
1339        }
1340        return super.getRowCountForAccessibility(recycler, state);
1341    }
1342
1343    @Override
1344    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
1345            RecyclerView.State state) {
1346        if (mOrientation == VERTICAL) {
1347            return mSpanCount;
1348        }
1349        return super.getColumnCountForAccessibility(recycler, state);
1350    }
1351
1352    /**
1353     * This is for internal use. Not necessarily the child closest to start but the first child
1354     * we find that matches the criteria.
1355     * This method does not do any sorting based on child's start coordinate, instead, it uses
1356     * children order.
1357     */
1358    View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
1359        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1360        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1361        final int limit = getChildCount();
1362        View partiallyVisible = null;
1363        for (int i = 0; i < limit; i++) {
1364            final View child = getChildAt(i);
1365            final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1366            final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1367            if (childEnd <= boundsStart || childStart >= boundsEnd) {
1368                continue; // not visible at all
1369            }
1370            if (childStart >= boundsStart || !fullyVisible) {
1371                // when checking for start, it is enough even if part of the child's top is visible
1372                // as long as fully visible is not requested.
1373                return child;
1374            }
1375            if (partiallyVisible == null) {
1376                partiallyVisible = child;
1377            }
1378        }
1379        return partiallyVisible;
1380    }
1381
1382    /**
1383     * This is for internal use. Not necessarily the child closest to bottom but the first child
1384     * we find that matches the criteria.
1385     * This method does not do any sorting based on child's end coordinate, instead, it uses
1386     * children order.
1387     */
1388    View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
1389        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1390        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1391        View partiallyVisible = null;
1392        for (int i = getChildCount() - 1; i >= 0; i--) {
1393            final View child = getChildAt(i);
1394            final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1395            final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1396            if (childEnd <= boundsStart || childStart >= boundsEnd) {
1397                continue; // not visible at all
1398            }
1399            if (childEnd <= boundsEnd || !fullyVisible) {
1400                // when checking for end, it is enough even if part of the child's bottom is visible
1401                // as long as fully visible is not requested.
1402                return child;
1403            }
1404            if (partiallyVisible == null) {
1405                partiallyVisible = child;
1406            }
1407        }
1408        return partiallyVisible;
1409    }
1410
1411    private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1412            boolean canOffsetChildren) {
1413        final int maxEndLine = getMaxEnd(Integer.MIN_VALUE);
1414        if (maxEndLine == Integer.MIN_VALUE) {
1415            return;
1416        }
1417        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
1418        int fixOffset;
1419        if (gap > 0) {
1420            fixOffset = -scrollBy(-gap, recycler, state);
1421        } else {
1422            return; // nothing to fix
1423        }
1424        gap -= fixOffset;
1425        if (canOffsetChildren && gap > 0) {
1426            mPrimaryOrientation.offsetChildren(gap);
1427        }
1428    }
1429
1430    private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1431            boolean canOffsetChildren) {
1432        final int minStartLine = getMinStart(Integer.MAX_VALUE);
1433        if (minStartLine == Integer.MAX_VALUE) {
1434            return;
1435        }
1436        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
1437        int fixOffset;
1438        if (gap > 0) {
1439            fixOffset = scrollBy(gap, recycler, state);
1440        } else {
1441            return; // nothing to fix
1442        }
1443        gap -= fixOffset;
1444        if (canOffsetChildren && gap > 0) {
1445            mPrimaryOrientation.offsetChildren(-gap);
1446        }
1447    }
1448
1449    private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
1450        mLayoutState.mAvailable = 0;
1451        mLayoutState.mCurrentPosition = anchorPosition;
1452        int startExtra = 0;
1453        int endExtra = 0;
1454        if (isSmoothScrolling()) {
1455            final int targetPos = state.getTargetScrollPosition();
1456            if (targetPos != RecyclerView.NO_POSITION) {
1457                if (mShouldReverseLayout == targetPos < anchorPosition) {
1458                    endExtra = mPrimaryOrientation.getTotalSpace();
1459                } else {
1460                    startExtra = mPrimaryOrientation.getTotalSpace();
1461                }
1462            }
1463        }
1464
1465        // Line of the furthest row.
1466        final boolean clipToPadding = getClipToPadding();
1467        if (clipToPadding) {
1468            mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
1469            mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
1470        } else {
1471            mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
1472            mLayoutState.mStartLine = -startExtra;
1473        }
1474        mLayoutState.mStopInFocusable = false;
1475        mLayoutState.mRecycle = true;
1476        mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED
1477                && mPrimaryOrientation.getEnd() == 0;
1478    }
1479
1480    private void setLayoutStateDirection(int direction) {
1481        mLayoutState.mLayoutDirection = direction;
1482        mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LayoutState.LAYOUT_START))
1483                ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD;
1484    }
1485
1486    @Override
1487    public void offsetChildrenHorizontal(int dx) {
1488        super.offsetChildrenHorizontal(dx);
1489        for (int i = 0; i < mSpanCount; i++) {
1490            mSpans[i].onOffset(dx);
1491        }
1492    }
1493
1494    @Override
1495    public void offsetChildrenVertical(int dy) {
1496        super.offsetChildrenVertical(dy);
1497        for (int i = 0; i < mSpanCount; i++) {
1498            mSpans[i].onOffset(dy);
1499        }
1500    }
1501
1502    @Override
1503    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
1504        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
1505    }
1506
1507    @Override
1508    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
1509        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
1510    }
1511
1512    @Override
1513    public void onItemsChanged(RecyclerView recyclerView) {
1514        mLazySpanLookup.clear();
1515        requestLayout();
1516    }
1517
1518    @Override
1519    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
1520        handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
1521    }
1522
1523    @Override
1524    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
1525            Object payload) {
1526        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
1527    }
1528
1529    /**
1530     * Checks whether it should invalidate span assignments in response to an adapter change.
1531     */
1532    private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
1533        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
1534        final int affectedRangeEnd; // exclusive
1535        final int affectedRangeStart; // inclusive
1536
1537        if (cmd == AdapterHelper.UpdateOp.MOVE) {
1538            if (positionStart < itemCountOrToPosition) {
1539                affectedRangeEnd = itemCountOrToPosition + 1;
1540                affectedRangeStart = positionStart;
1541            } else {
1542                affectedRangeEnd = positionStart + 1;
1543                affectedRangeStart = itemCountOrToPosition;
1544            }
1545        } else {
1546            affectedRangeStart = positionStart;
1547            affectedRangeEnd = positionStart + itemCountOrToPosition;
1548        }
1549
1550        mLazySpanLookup.invalidateAfter(affectedRangeStart);
1551        switch (cmd) {
1552            case AdapterHelper.UpdateOp.ADD:
1553                mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
1554                break;
1555            case AdapterHelper.UpdateOp.REMOVE:
1556                mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
1557                break;
1558            case AdapterHelper.UpdateOp.MOVE:
1559                // TODO optimize
1560                mLazySpanLookup.offsetForRemoval(positionStart, 1);
1561                mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
1562                break;
1563        }
1564
1565        if (affectedRangeEnd <= minPosition) {
1566            return;
1567        }
1568
1569        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
1570        if (affectedRangeStart <= maxPosition) {
1571            requestLayout();
1572        }
1573    }
1574
1575    private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
1576            RecyclerView.State state) {
1577        mRemainingSpans.set(0, mSpanCount, true);
1578        // The target position we are trying to reach.
1579        final int targetLine;
1580
1581        // Line of the furthest row.
1582        if (mLayoutState.mInfinite) {
1583            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1584                targetLine = Integer.MAX_VALUE;
1585            } else { // LAYOUT_START
1586                targetLine = Integer.MIN_VALUE;
1587            }
1588        } else {
1589            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1590                targetLine = layoutState.mEndLine + layoutState.mAvailable;
1591            } else { // LAYOUT_START
1592                targetLine = layoutState.mStartLine - layoutState.mAvailable;
1593            }
1594        }
1595
1596        updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
1597        if (DEBUG) {
1598            Log.d(TAG, "FILLING targetLine: " + targetLine + ","
1599                    + "remaining spans:" + mRemainingSpans + ", state: " + layoutState);
1600        }
1601
1602        // the default coordinate to add new view.
1603        final int defaultNewViewLine = mShouldReverseLayout
1604                ? mPrimaryOrientation.getEndAfterPadding()
1605                : mPrimaryOrientation.getStartAfterPadding();
1606        boolean added = false;
1607        while (layoutState.hasMore(state)
1608                && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) {
1609            View view = layoutState.next(recycler);
1610            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
1611            final int position = lp.getViewLayoutPosition();
1612            final int spanIndex = mLazySpanLookup.getSpan(position);
1613            Span currentSpan;
1614            final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
1615            if (assignSpan) {
1616                currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
1617                mLazySpanLookup.setSpan(position, currentSpan);
1618                if (DEBUG) {
1619                    Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
1620                }
1621            } else {
1622                if (DEBUG) {
1623                    Log.d(TAG, "using " + spanIndex + " for pos " + position);
1624                }
1625                currentSpan = mSpans[spanIndex];
1626            }
1627            // assign span before measuring so that item decorators can get updated span index
1628            lp.mSpan = currentSpan;
1629            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1630                addView(view);
1631            } else {
1632                addView(view, 0);
1633            }
1634            measureChildWithDecorationsAndMargin(view, lp, false);
1635
1636            final int start;
1637            final int end;
1638            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1639                start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
1640                        : currentSpan.getEndLine(defaultNewViewLine);
1641                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
1642                if (assignSpan && lp.mFullSpan) {
1643                    LazySpanLookup.FullSpanItem fullSpanItem;
1644                    fullSpanItem = createFullSpanItemFromEnd(start);
1645                    fullSpanItem.mGapDir = LayoutState.LAYOUT_START;
1646                    fullSpanItem.mPosition = position;
1647                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1648                }
1649            } else {
1650                end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
1651                        : currentSpan.getStartLine(defaultNewViewLine);
1652                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
1653                if (assignSpan && lp.mFullSpan) {
1654                    LazySpanLookup.FullSpanItem fullSpanItem;
1655                    fullSpanItem = createFullSpanItemFromStart(end);
1656                    fullSpanItem.mGapDir = LayoutState.LAYOUT_END;
1657                    fullSpanItem.mPosition = position;
1658                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1659                }
1660            }
1661
1662            // check if this item may create gaps in the future
1663            if (lp.mFullSpan && layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_HEAD) {
1664                if (assignSpan) {
1665                    mLaidOutInvalidFullSpan = true;
1666                } else {
1667                    final boolean hasInvalidGap;
1668                    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1669                        hasInvalidGap = !areAllEndsEqual();
1670                    } else { // layoutState.mLayoutDirection == LAYOUT_START
1671                        hasInvalidGap = !areAllStartsEqual();
1672                    }
1673                    if (hasInvalidGap) {
1674                        final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
1675                                .getFullSpanItem(position);
1676                        if (fullSpanItem != null) {
1677                            fullSpanItem.mHasUnwantedGapAfter = true;
1678                        }
1679                        mLaidOutInvalidFullSpan = true;
1680                    }
1681                }
1682            }
1683            attachViewToSpans(view, lp, layoutState);
1684            final int otherStart;
1685            final int otherEnd;
1686            if (isLayoutRTL() && mOrientation == VERTICAL) {
1687                otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() :
1688                        mSecondaryOrientation.getEndAfterPadding()
1689                                - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan;
1690                otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view);
1691            } else {
1692                otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
1693                        : currentSpan.mIndex * mSizePerSpan
1694                                + mSecondaryOrientation.getStartAfterPadding();
1695                otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
1696            }
1697
1698            if (mOrientation == VERTICAL) {
1699                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
1700            } else {
1701                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
1702            }
1703
1704            if (lp.mFullSpan) {
1705                updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
1706            } else {
1707                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
1708            }
1709            recycle(recycler, mLayoutState);
1710            if (mLayoutState.mStopInFocusable && view.hasFocusable()) {
1711                if (lp.mFullSpan) {
1712                    mRemainingSpans.clear();
1713                } else {
1714                    mRemainingSpans.set(currentSpan.mIndex, false);
1715                }
1716            }
1717            added = true;
1718        }
1719        if (!added) {
1720            recycle(recycler, mLayoutState);
1721        }
1722        final int diff;
1723        if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1724            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1725            diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
1726        } else {
1727            final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1728            diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
1729        }
1730        return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
1731    }
1732
1733    private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
1734        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1735        fsi.mGapPerSpan = new int[mSpanCount];
1736        for (int i = 0; i < mSpanCount; i++) {
1737            fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
1738        }
1739        return fsi;
1740    }
1741
1742    private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
1743        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1744        fsi.mGapPerSpan = new int[mSpanCount];
1745        for (int i = 0; i < mSpanCount; i++) {
1746            fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
1747        }
1748        return fsi;
1749    }
1750
1751    private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) {
1752        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1753            if (lp.mFullSpan) {
1754                appendViewToAllSpans(view);
1755            } else {
1756                lp.mSpan.appendToSpan(view);
1757            }
1758        } else {
1759            if (lp.mFullSpan) {
1760                prependViewToAllSpans(view);
1761            } else {
1762                lp.mSpan.prependToSpan(view);
1763            }
1764        }
1765    }
1766
1767    private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
1768        if (!layoutState.mRecycle || layoutState.mInfinite) {
1769            return;
1770        }
1771        if (layoutState.mAvailable == 0) {
1772            // easy, recycle line is still valid
1773            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1774                recycleFromEnd(recycler, layoutState.mEndLine);
1775            } else {
1776                recycleFromStart(recycler, layoutState.mStartLine);
1777            }
1778        } else {
1779            // scrolling case, recycle line can be shifted by how much space we could cover
1780            // by adding new views
1781            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
1782                // calculate recycle line
1783                int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
1784                final int line;
1785                if (scrolled < 0) {
1786                    line = layoutState.mEndLine;
1787                } else {
1788                    line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
1789                }
1790                recycleFromEnd(recycler, line);
1791            } else {
1792                // calculate recycle line
1793                int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
1794                final int line;
1795                if (scrolled < 0) {
1796                    line = layoutState.mStartLine;
1797                } else {
1798                    line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
1799                }
1800                recycleFromStart(recycler, line);
1801            }
1802        }
1803
1804    }
1805
1806    private void appendViewToAllSpans(View view) {
1807        // traverse in reverse so that we end up assigning full span items to 0
1808        for (int i = mSpanCount - 1; i >= 0; i--) {
1809            mSpans[i].appendToSpan(view);
1810        }
1811    }
1812
1813    private void prependViewToAllSpans(View view) {
1814        // traverse in reverse so that we end up assigning full span items to 0
1815        for (int i = mSpanCount - 1; i >= 0; i--) {
1816            mSpans[i].prependToSpan(view);
1817        }
1818    }
1819
1820    private void updateAllRemainingSpans(int layoutDir, int targetLine) {
1821        for (int i = 0; i < mSpanCount; i++) {
1822            if (mSpans[i].mViews.isEmpty()) {
1823                continue;
1824            }
1825            updateRemainingSpans(mSpans[i], layoutDir, targetLine);
1826        }
1827    }
1828
1829    private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
1830        final int deletedSize = span.getDeletedSize();
1831        if (layoutDir == LayoutState.LAYOUT_START) {
1832            final int line = span.getStartLine();
1833            if (line + deletedSize <= targetLine) {
1834                mRemainingSpans.set(span.mIndex, false);
1835            }
1836        } else {
1837            final int line = span.getEndLine();
1838            if (line - deletedSize >= targetLine) {
1839                mRemainingSpans.set(span.mIndex, false);
1840            }
1841        }
1842    }
1843
1844    private int getMaxStart(int def) {
1845        int maxStart = mSpans[0].getStartLine(def);
1846        for (int i = 1; i < mSpanCount; i++) {
1847            final int spanStart = mSpans[i].getStartLine(def);
1848            if (spanStart > maxStart) {
1849                maxStart = spanStart;
1850            }
1851        }
1852        return maxStart;
1853    }
1854
1855    private int getMinStart(int def) {
1856        int minStart = mSpans[0].getStartLine(def);
1857        for (int i = 1; i < mSpanCount; i++) {
1858            final int spanStart = mSpans[i].getStartLine(def);
1859            if (spanStart < minStart) {
1860                minStart = spanStart;
1861            }
1862        }
1863        return minStart;
1864    }
1865
1866    boolean areAllEndsEqual() {
1867        int end = mSpans[0].getEndLine(Span.INVALID_LINE);
1868        for (int i = 1; i < mSpanCount; i++) {
1869            if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
1870                return false;
1871            }
1872        }
1873        return true;
1874    }
1875
1876    boolean areAllStartsEqual() {
1877        int start = mSpans[0].getStartLine(Span.INVALID_LINE);
1878        for (int i = 1; i < mSpanCount; i++) {
1879            if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
1880                return false;
1881            }
1882        }
1883        return true;
1884    }
1885
1886    private int getMaxEnd(int def) {
1887        int maxEnd = mSpans[0].getEndLine(def);
1888        for (int i = 1; i < mSpanCount; i++) {
1889            final int spanEnd = mSpans[i].getEndLine(def);
1890            if (spanEnd > maxEnd) {
1891                maxEnd = spanEnd;
1892            }
1893        }
1894        return maxEnd;
1895    }
1896
1897    private int getMinEnd(int def) {
1898        int minEnd = mSpans[0].getEndLine(def);
1899        for (int i = 1; i < mSpanCount; i++) {
1900            final int spanEnd = mSpans[i].getEndLine(def);
1901            if (spanEnd < minEnd) {
1902                minEnd = spanEnd;
1903            }
1904        }
1905        return minEnd;
1906    }
1907
1908    private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
1909        while (getChildCount() > 0) {
1910            View child = getChildAt(0);
1911            if (mPrimaryOrientation.getDecoratedEnd(child) <= line
1912                    && mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) {
1913                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1914                // Don't recycle the last View in a span not to lose span's start/end lines
1915                if (lp.mFullSpan) {
1916                    for (int j = 0; j < mSpanCount; j++) {
1917                        if (mSpans[j].mViews.size() == 1) {
1918                            return;
1919                        }
1920                    }
1921                    for (int j = 0; j < mSpanCount; j++) {
1922                        mSpans[j].popStart();
1923                    }
1924                } else {
1925                    if (lp.mSpan.mViews.size() == 1) {
1926                        return;
1927                    }
1928                    lp.mSpan.popStart();
1929                }
1930                removeAndRecycleView(child, recycler);
1931            } else {
1932                return; // done
1933            }
1934        }
1935    }
1936
1937    private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
1938        final int childCount = getChildCount();
1939        int i;
1940        for (i = childCount - 1; i >= 0; i--) {
1941            View child = getChildAt(i);
1942            if (mPrimaryOrientation.getDecoratedStart(child) >= line
1943                    && mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) {
1944                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1945                // Don't recycle the last View in a span not to lose span's start/end lines
1946                if (lp.mFullSpan) {
1947                    for (int j = 0; j < mSpanCount; j++) {
1948                        if (mSpans[j].mViews.size() == 1) {
1949                            return;
1950                        }
1951                    }
1952                    for (int j = 0; j < mSpanCount; j++) {
1953                        mSpans[j].popEnd();
1954                    }
1955                } else {
1956                    if (lp.mSpan.mViews.size() == 1) {
1957                        return;
1958                    }
1959                    lp.mSpan.popEnd();
1960                }
1961                removeAndRecycleView(child, recycler);
1962            } else {
1963                return; // done
1964            }
1965        }
1966    }
1967
1968    /**
1969     * @return True if last span is the first one we want to fill
1970     */
1971    private boolean preferLastSpan(int layoutDir) {
1972        if (mOrientation == HORIZONTAL) {
1973            return (layoutDir == LayoutState.LAYOUT_START) != mShouldReverseLayout;
1974        }
1975        return ((layoutDir == LayoutState.LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
1976    }
1977
1978    /**
1979     * Finds the span for the next view.
1980     */
1981    private Span getNextSpan(LayoutState layoutState) {
1982        final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
1983        final int startIndex, endIndex, diff;
1984        if (preferLastSpan) {
1985            startIndex = mSpanCount - 1;
1986            endIndex = -1;
1987            diff = -1;
1988        } else {
1989            startIndex = 0;
1990            endIndex = mSpanCount;
1991            diff = 1;
1992        }
1993        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1994            Span min = null;
1995            int minLine = Integer.MAX_VALUE;
1996            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
1997            for (int i = startIndex; i != endIndex; i += diff) {
1998                final Span other = mSpans[i];
1999                int otherLine = other.getEndLine(defaultLine);
2000                if (otherLine < minLine) {
2001                    min = other;
2002                    minLine = otherLine;
2003                }
2004            }
2005            return min;
2006        } else {
2007            Span max = null;
2008            int maxLine = Integer.MIN_VALUE;
2009            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
2010            for (int i = startIndex; i != endIndex; i += diff) {
2011                final Span other = mSpans[i];
2012                int otherLine = other.getStartLine(defaultLine);
2013                if (otherLine > maxLine) {
2014                    max = other;
2015                    maxLine = otherLine;
2016                }
2017            }
2018            return max;
2019        }
2020    }
2021
2022    @Override
2023    public boolean canScrollVertically() {
2024        return mOrientation == VERTICAL;
2025    }
2026
2027    @Override
2028    public boolean canScrollHorizontally() {
2029        return mOrientation == HORIZONTAL;
2030    }
2031
2032    @Override
2033    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
2034            RecyclerView.State state) {
2035        return scrollBy(dx, recycler, state);
2036    }
2037
2038    @Override
2039    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
2040            RecyclerView.State state) {
2041        return scrollBy(dy, recycler, state);
2042    }
2043
2044    private int calculateScrollDirectionForPosition(int position) {
2045        if (getChildCount() == 0) {
2046            return mShouldReverseLayout ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
2047        }
2048        final int firstChildPos = getFirstChildPosition();
2049        return position < firstChildPos != mShouldReverseLayout ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
2050    }
2051
2052    @Override
2053    public PointF computeScrollVectorForPosition(int targetPosition) {
2054        final int direction = calculateScrollDirectionForPosition(targetPosition);
2055        PointF outVector = new PointF();
2056        if (direction == 0) {
2057            return null;
2058        }
2059        if (mOrientation == HORIZONTAL) {
2060            outVector.x = direction;
2061            outVector.y = 0;
2062        } else {
2063            outVector.x = 0;
2064            outVector.y = direction;
2065        }
2066        return outVector;
2067    }
2068
2069    @Override
2070    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
2071            int position) {
2072        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext());
2073        scroller.setTargetPosition(position);
2074        startSmoothScroll(scroller);
2075    }
2076
2077    @Override
2078    public void scrollToPosition(int position) {
2079        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
2080            mPendingSavedState.invalidateAnchorPositionInfo();
2081        }
2082        mPendingScrollPosition = position;
2083        mPendingScrollPositionOffset = INVALID_OFFSET;
2084        requestLayout();
2085    }
2086
2087    /**
2088     * Scroll to the specified adapter position with the given offset from layout start.
2089     * <p>
2090     * Note that scroll position change will not be reflected until the next layout call.
2091     * <p>
2092     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
2093     *
2094     * @param position Index (starting at 0) of the reference item.
2095     * @param offset   The distance (in pixels) between the start edge of the item view and
2096     *                 start edge of the RecyclerView.
2097     * @see #setReverseLayout(boolean)
2098     * @see #scrollToPosition(int)
2099     */
2100    public void scrollToPositionWithOffset(int position, int offset) {
2101        if (mPendingSavedState != null) {
2102            mPendingSavedState.invalidateAnchorPositionInfo();
2103        }
2104        mPendingScrollPosition = position;
2105        mPendingScrollPositionOffset = offset;
2106        requestLayout();
2107    }
2108
2109    /** @hide */
2110    @Override
2111    @RestrictTo(LIBRARY)
2112    public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
2113            LayoutPrefetchRegistry layoutPrefetchRegistry) {
2114        /* This method uses the simplifying assumption that the next N items (where N = span count)
2115         * will be assigned, one-to-one, to spans, where ordering is based on which span  extends
2116         * least beyond the viewport.
2117         *
2118         * While this simplified model will be incorrect in some cases, it's difficult to know
2119         * item heights, or whether individual items will be full span prior to construction.
2120         *
2121         * While this greedy estimation approach may underestimate the distance to prefetch items,
2122         * it's very unlikely to overestimate them, so distances can be conservatively used to know
2123         * the soonest (in terms of scroll distance) a prefetched view may come on screen.
2124         */
2125        int delta = (mOrientation == HORIZONTAL) ? dx : dy;
2126        if (getChildCount() == 0 || delta == 0) {
2127            // can't support this scroll, so don't bother prefetching
2128            return;
2129        }
2130        prepareLayoutStateForDelta(delta, state);
2131
2132        // build sorted list of distances to end of each span (though we don't care which is which)
2133        if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
2134            mPrefetchDistances = new int[mSpanCount];
2135        }
2136
2137        int itemPrefetchCount = 0;
2138        for (int i = 0; i < mSpanCount; i++) {
2139            // compute number of pixels past the edge of the viewport that the current span extends
2140            int distance = mLayoutState.mItemDirection == LayoutState.LAYOUT_START
2141                    ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
2142                    : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
2143            if (distance >= 0) {
2144                // span extends to the edge, so prefetch next item
2145                mPrefetchDistances[itemPrefetchCount] = distance;
2146                itemPrefetchCount++;
2147            }
2148        }
2149        Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
2150
2151        // then assign them in order to the next N views (where N = span count)
2152        for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
2153            layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition,
2154                    mPrefetchDistances[i]);
2155            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
2156        }
2157    }
2158
2159    void prepareLayoutStateForDelta(int delta, RecyclerView.State state) {
2160        final int referenceChildPosition;
2161        final int layoutDir;
2162        if (delta > 0) { // layout towards end
2163            layoutDir = LayoutState.LAYOUT_END;
2164            referenceChildPosition = getLastChildPosition();
2165        } else {
2166            layoutDir = LayoutState.LAYOUT_START;
2167            referenceChildPosition = getFirstChildPosition();
2168        }
2169        mLayoutState.mRecycle = true;
2170        updateLayoutState(referenceChildPosition, state);
2171        setLayoutStateDirection(layoutDir);
2172        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
2173        mLayoutState.mAvailable = Math.abs(delta);
2174    }
2175
2176    int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
2177        if (getChildCount() == 0 || dt == 0) {
2178            return 0;
2179        }
2180
2181        prepareLayoutStateForDelta(dt, state);
2182        int consumed = fill(recycler, mLayoutState, state);
2183        final int available = mLayoutState.mAvailable;
2184        final int totalScroll;
2185        if (available < consumed) {
2186            totalScroll = dt;
2187        } else if (dt < 0) {
2188            totalScroll = -consumed;
2189        } else { // dt > 0
2190            totalScroll = consumed;
2191        }
2192        if (DEBUG) {
2193            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
2194        }
2195
2196        mPrimaryOrientation.offsetChildren(-totalScroll);
2197        // always reset this if we scroll for a proper save instance state
2198        mLastLayoutFromEnd = mShouldReverseLayout;
2199        mLayoutState.mAvailable = 0;
2200        recycle(recycler, mLayoutState);
2201        return totalScroll;
2202    }
2203
2204    int getLastChildPosition() {
2205        final int childCount = getChildCount();
2206        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
2207    }
2208
2209    int getFirstChildPosition() {
2210        final int childCount = getChildCount();
2211        return childCount == 0 ? 0 : getPosition(getChildAt(0));
2212    }
2213
2214    /**
2215     * Finds the first View that can be used as an anchor View.
2216     *
2217     * @return Position of the View or 0 if it cannot find any such View.
2218     */
2219    private int findFirstReferenceChildPosition(int itemCount) {
2220        final int limit = getChildCount();
2221        for (int i = 0; i < limit; i++) {
2222            final View view = getChildAt(i);
2223            final int position = getPosition(view);
2224            if (position >= 0 && position < itemCount) {
2225                return position;
2226            }
2227        }
2228        return 0;
2229    }
2230
2231    /**
2232     * Finds the last View that can be used as an anchor View.
2233     *
2234     * @return Position of the View or 0 if it cannot find any such View.
2235     */
2236    private int findLastReferenceChildPosition(int itemCount) {
2237        for (int i = getChildCount() - 1; i >= 0; i--) {
2238            final View view = getChildAt(i);
2239            final int position = getPosition(view);
2240            if (position >= 0 && position < itemCount) {
2241                return position;
2242            }
2243        }
2244        return 0;
2245    }
2246
2247    @SuppressWarnings("deprecation")
2248    @Override
2249    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
2250        if (mOrientation == HORIZONTAL) {
2251            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
2252                    ViewGroup.LayoutParams.MATCH_PARENT);
2253        } else {
2254            return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
2255                    ViewGroup.LayoutParams.WRAP_CONTENT);
2256        }
2257    }
2258
2259    @Override
2260    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
2261        return new LayoutParams(c, attrs);
2262    }
2263
2264    @Override
2265    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
2266        if (lp instanceof ViewGroup.MarginLayoutParams) {
2267            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
2268        } else {
2269            return new LayoutParams(lp);
2270        }
2271    }
2272
2273    @Override
2274    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
2275        return lp instanceof LayoutParams;
2276    }
2277
2278    public int getOrientation() {
2279        return mOrientation;
2280    }
2281
2282    @Nullable
2283    @Override
2284    public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
2285            RecyclerView.State state) {
2286        if (getChildCount() == 0) {
2287            return null;
2288        }
2289
2290        final View directChild = findContainingItemView(focused);
2291        if (directChild == null) {
2292            return null;
2293        }
2294
2295        resolveShouldLayoutReverse();
2296        final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
2297        if (layoutDir == LayoutState.INVALID_LAYOUT) {
2298            return null;
2299        }
2300        LayoutParams prevFocusLayoutParams = (LayoutParams) directChild.getLayoutParams();
2301        boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan;
2302        final Span prevFocusSpan = prevFocusLayoutParams.mSpan;
2303        final int referenceChildPosition;
2304        if (layoutDir == LayoutState.LAYOUT_END) { // layout towards end
2305            referenceChildPosition = getLastChildPosition();
2306        } else {
2307            referenceChildPosition = getFirstChildPosition();
2308        }
2309        updateLayoutState(referenceChildPosition, state);
2310        setLayoutStateDirection(layoutDir);
2311
2312        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
2313        mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace());
2314        mLayoutState.mStopInFocusable = true;
2315        mLayoutState.mRecycle = false;
2316        fill(recycler, mLayoutState, state);
2317        mLastLayoutFromEnd = mShouldReverseLayout;
2318        if (!prevFocusFullSpan) {
2319            View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir);
2320            if (view != null && view != directChild) {
2321                return view;
2322            }
2323        }
2324
2325        // either could not find from the desired span or prev view is full span.
2326        // traverse all spans
2327        if (preferLastSpan(layoutDir)) {
2328            for (int i = mSpanCount - 1; i >= 0; i--) {
2329                View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
2330                if (view != null && view != directChild) {
2331                    return view;
2332                }
2333            }
2334        } else {
2335            for (int i = 0; i < mSpanCount; i++) {
2336                View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
2337                if (view != null && view != directChild) {
2338                    return view;
2339                }
2340            }
2341        }
2342
2343        // Could not find any focusable views from any of the existing spans. Now start the search
2344        // to find the best unfocusable candidate to become visible on the screen next. The search
2345        // is done in the same fashion: first, check the views in the desired span and if no
2346        // candidate is found, traverse the views in all the remaining spans.
2347        boolean shouldSearchFromStart = !mReverseLayout == (layoutDir == LayoutState.LAYOUT_START);
2348        View unfocusableCandidate = null;
2349        if (!prevFocusFullSpan) {
2350            unfocusableCandidate = findViewByPosition(shouldSearchFromStart
2351                    ? prevFocusSpan.findFirstPartiallyVisibleItemPosition() :
2352                    prevFocusSpan.findLastPartiallyVisibleItemPosition());
2353            if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
2354                return unfocusableCandidate;
2355            }
2356        }
2357
2358        if (preferLastSpan(layoutDir)) {
2359            for (int i = mSpanCount - 1; i >= 0; i--) {
2360                if (i == prevFocusSpan.mIndex) {
2361                    continue;
2362                }
2363                unfocusableCandidate = findViewByPosition(shouldSearchFromStart
2364                        ? mSpans[i].findFirstPartiallyVisibleItemPosition() :
2365                        mSpans[i].findLastPartiallyVisibleItemPosition());
2366                if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
2367                    return unfocusableCandidate;
2368                }
2369            }
2370        } else {
2371            for (int i = 0; i < mSpanCount; i++) {
2372                unfocusableCandidate = findViewByPosition(shouldSearchFromStart
2373                        ? mSpans[i].findFirstPartiallyVisibleItemPosition() :
2374                        mSpans[i].findLastPartiallyVisibleItemPosition());
2375                if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
2376                    return unfocusableCandidate;
2377                }
2378            }
2379        }
2380        return null;
2381    }
2382
2383    /**
2384     * Converts a focusDirection to orientation.
2385     *
2386     * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
2387     *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
2388     *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
2389     *                       or 0 for not applicable
2390     * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
2391     * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
2392     */
2393    private int convertFocusDirectionToLayoutDirection(int focusDirection) {
2394        switch (focusDirection) {
2395            case View.FOCUS_BACKWARD:
2396                if (mOrientation == VERTICAL) {
2397                    return LayoutState.LAYOUT_START;
2398                } else if (isLayoutRTL()) {
2399                    return LayoutState.LAYOUT_END;
2400                } else {
2401                    return LayoutState.LAYOUT_START;
2402                }
2403            case View.FOCUS_FORWARD:
2404                if (mOrientation == VERTICAL) {
2405                    return LayoutState.LAYOUT_END;
2406                } else if (isLayoutRTL()) {
2407                    return LayoutState.LAYOUT_START;
2408                } else {
2409                    return LayoutState.LAYOUT_END;
2410                }
2411            case View.FOCUS_UP:
2412                return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
2413                        : LayoutState.INVALID_LAYOUT;
2414            case View.FOCUS_DOWN:
2415                return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
2416                        : LayoutState.INVALID_LAYOUT;
2417            case View.FOCUS_LEFT:
2418                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
2419                        : LayoutState.INVALID_LAYOUT;
2420            case View.FOCUS_RIGHT:
2421                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
2422                        : LayoutState.INVALID_LAYOUT;
2423            default:
2424                if (DEBUG) {
2425                    Log.d(TAG, "Unknown focus request:" + focusDirection);
2426                }
2427                return LayoutState.INVALID_LAYOUT;
2428        }
2429
2430    }
2431
2432    /**
2433     * LayoutParams used by StaggeredGridLayoutManager.
2434     * <p>
2435     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
2436     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
2437     * expected to fill all of the space given to it.
2438     */
2439    public static class LayoutParams extends RecyclerView.LayoutParams {
2440
2441        /**
2442         * Span Id for Views that are not laid out yet.
2443         */
2444        public static final int INVALID_SPAN_ID = -1;
2445
2446        // Package scope to be able to access from tests.
2447        Span mSpan;
2448
2449        boolean mFullSpan;
2450
2451        public LayoutParams(Context c, AttributeSet attrs) {
2452            super(c, attrs);
2453        }
2454
2455        public LayoutParams(int width, int height) {
2456            super(width, height);
2457        }
2458
2459        public LayoutParams(ViewGroup.MarginLayoutParams source) {
2460            super(source);
2461        }
2462
2463        public LayoutParams(ViewGroup.LayoutParams source) {
2464            super(source);
2465        }
2466
2467        public LayoutParams(RecyclerView.LayoutParams source) {
2468            super(source);
2469        }
2470
2471        /**
2472         * When set to true, the item will layout using all span area. That means, if orientation
2473         * is vertical, the view will have full width; if orientation is horizontal, the view will
2474         * have full height.
2475         *
2476         * @param fullSpan True if this item should traverse all spans.
2477         * @see #isFullSpan()
2478         */
2479        public void setFullSpan(boolean fullSpan) {
2480            mFullSpan = fullSpan;
2481        }
2482
2483        /**
2484         * Returns whether this View occupies all available spans or just one.
2485         *
2486         * @return True if the View occupies all spans or false otherwise.
2487         * @see #setFullSpan(boolean)
2488         */
2489        public boolean isFullSpan() {
2490            return mFullSpan;
2491        }
2492
2493        /**
2494         * Returns the Span index to which this View is assigned.
2495         *
2496         * @return The Span index of the View. If View is not yet assigned to any span, returns
2497         * {@link #INVALID_SPAN_ID}.
2498         */
2499        public final int getSpanIndex() {
2500            if (mSpan == null) {
2501                return INVALID_SPAN_ID;
2502            }
2503            return mSpan.mIndex;
2504        }
2505    }
2506
2507    // Package scoped to access from tests.
2508    class Span {
2509
2510        static final int INVALID_LINE = Integer.MIN_VALUE;
2511        ArrayList<View> mViews = new ArrayList<>();
2512        int mCachedStart = INVALID_LINE;
2513        int mCachedEnd = INVALID_LINE;
2514        int mDeletedSize = 0;
2515        final int mIndex;
2516
2517        Span(int index) {
2518            mIndex = index;
2519        }
2520
2521        int getStartLine(int def) {
2522            if (mCachedStart != INVALID_LINE) {
2523                return mCachedStart;
2524            }
2525            if (mViews.size() == 0) {
2526                return def;
2527            }
2528            calculateCachedStart();
2529            return mCachedStart;
2530        }
2531
2532        void calculateCachedStart() {
2533            final View startView = mViews.get(0);
2534            final LayoutParams lp = getLayoutParams(startView);
2535            mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
2536            if (lp.mFullSpan) {
2537                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2538                        .getFullSpanItem(lp.getViewLayoutPosition());
2539                if (fsi != null && fsi.mGapDir == LayoutState.LAYOUT_START) {
2540                    mCachedStart -= fsi.getGapForSpan(mIndex);
2541                }
2542            }
2543        }
2544
2545        // Use this one when default value does not make sense and not having a value means a bug.
2546        int getStartLine() {
2547            if (mCachedStart != INVALID_LINE) {
2548                return mCachedStart;
2549            }
2550            calculateCachedStart();
2551            return mCachedStart;
2552        }
2553
2554        int getEndLine(int def) {
2555            if (mCachedEnd != INVALID_LINE) {
2556                return mCachedEnd;
2557            }
2558            final int size = mViews.size();
2559            if (size == 0) {
2560                return def;
2561            }
2562            calculateCachedEnd();
2563            return mCachedEnd;
2564        }
2565
2566        void calculateCachedEnd() {
2567            final View endView = mViews.get(mViews.size() - 1);
2568            final LayoutParams lp = getLayoutParams(endView);
2569            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
2570            if (lp.mFullSpan) {
2571                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2572                        .getFullSpanItem(lp.getViewLayoutPosition());
2573                if (fsi != null && fsi.mGapDir == LayoutState.LAYOUT_END) {
2574                    mCachedEnd += fsi.getGapForSpan(mIndex);
2575                }
2576            }
2577        }
2578
2579        // Use this one when default value does not make sense and not having a value means a bug.
2580        int getEndLine() {
2581            if (mCachedEnd != INVALID_LINE) {
2582                return mCachedEnd;
2583            }
2584            calculateCachedEnd();
2585            return mCachedEnd;
2586        }
2587
2588        void prependToSpan(View view) {
2589            LayoutParams lp = getLayoutParams(view);
2590            lp.mSpan = this;
2591            mViews.add(0, view);
2592            mCachedStart = INVALID_LINE;
2593            if (mViews.size() == 1) {
2594                mCachedEnd = INVALID_LINE;
2595            }
2596            if (lp.isItemRemoved() || lp.isItemChanged()) {
2597                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2598            }
2599        }
2600
2601        void appendToSpan(View view) {
2602            LayoutParams lp = getLayoutParams(view);
2603            lp.mSpan = this;
2604            mViews.add(view);
2605            mCachedEnd = INVALID_LINE;
2606            if (mViews.size() == 1) {
2607                mCachedStart = INVALID_LINE;
2608            }
2609            if (lp.isItemRemoved() || lp.isItemChanged()) {
2610                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2611            }
2612        }
2613
2614        // Useful method to preserve positions on a re-layout.
2615        void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
2616            int reference;
2617            if (reverseLayout) {
2618                reference = getEndLine(INVALID_LINE);
2619            } else {
2620                reference = getStartLine(INVALID_LINE);
2621            }
2622            clear();
2623            if (reference == INVALID_LINE) {
2624                return;
2625            }
2626            if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding())
2627                    || (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
2628                return;
2629            }
2630            if (offset != INVALID_OFFSET) {
2631                reference += offset;
2632            }
2633            mCachedStart = mCachedEnd = reference;
2634        }
2635
2636        void clear() {
2637            mViews.clear();
2638            invalidateCache();
2639            mDeletedSize = 0;
2640        }
2641
2642        void invalidateCache() {
2643            mCachedStart = INVALID_LINE;
2644            mCachedEnd = INVALID_LINE;
2645        }
2646
2647        void setLine(int line) {
2648            mCachedEnd = mCachedStart = line;
2649        }
2650
2651        void popEnd() {
2652            final int size = mViews.size();
2653            View end = mViews.remove(size - 1);
2654            final LayoutParams lp = getLayoutParams(end);
2655            lp.mSpan = null;
2656            if (lp.isItemRemoved() || lp.isItemChanged()) {
2657                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
2658            }
2659            if (size == 1) {
2660                mCachedStart = INVALID_LINE;
2661            }
2662            mCachedEnd = INVALID_LINE;
2663        }
2664
2665        void popStart() {
2666            View start = mViews.remove(0);
2667            final LayoutParams lp = getLayoutParams(start);
2668            lp.mSpan = null;
2669            if (mViews.size() == 0) {
2670                mCachedEnd = INVALID_LINE;
2671            }
2672            if (lp.isItemRemoved() || lp.isItemChanged()) {
2673                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
2674            }
2675            mCachedStart = INVALID_LINE;
2676        }
2677
2678        public int getDeletedSize() {
2679            return mDeletedSize;
2680        }
2681
2682        LayoutParams getLayoutParams(View view) {
2683            return (LayoutParams) view.getLayoutParams();
2684        }
2685
2686        void onOffset(int dt) {
2687            if (mCachedStart != INVALID_LINE) {
2688                mCachedStart += dt;
2689            }
2690            if (mCachedEnd != INVALID_LINE) {
2691                mCachedEnd += dt;
2692            }
2693        }
2694
2695        public int findFirstVisibleItemPosition() {
2696            return mReverseLayout
2697                    ? findOneVisibleChild(mViews.size() - 1, -1, false)
2698                    : findOneVisibleChild(0, mViews.size(), false);
2699        }
2700
2701        public int findFirstPartiallyVisibleItemPosition() {
2702            return mReverseLayout
2703                    ? findOnePartiallyVisibleChild(mViews.size() - 1, -1, true)
2704                    : findOnePartiallyVisibleChild(0, mViews.size(), true);
2705        }
2706
2707        public int findFirstCompletelyVisibleItemPosition() {
2708            return mReverseLayout
2709                    ? findOneVisibleChild(mViews.size() - 1, -1, true)
2710                    : findOneVisibleChild(0, mViews.size(), true);
2711        }
2712
2713        public int findLastVisibleItemPosition() {
2714            return mReverseLayout
2715                    ? findOneVisibleChild(0, mViews.size(), false)
2716                    : findOneVisibleChild(mViews.size() - 1, -1, false);
2717        }
2718
2719        public int findLastPartiallyVisibleItemPosition() {
2720            return mReverseLayout
2721                    ? findOnePartiallyVisibleChild(0, mViews.size(), true)
2722                    : findOnePartiallyVisibleChild(mViews.size() - 1, -1, true);
2723        }
2724
2725        public int findLastCompletelyVisibleItemPosition() {
2726            return mReverseLayout
2727                    ? findOneVisibleChild(0, mViews.size(), true)
2728                    : findOneVisibleChild(mViews.size() - 1, -1, true);
2729        }
2730
2731        /**
2732         * Returns the first view within this span that is partially or fully visible. Partially
2733         * visible refers to a view that overlaps but is not fully contained within RV's padded
2734         * bounded area. This view returned can be defined to have an area of overlap strictly
2735         * greater than zero if acceptEndPointInclusion is false. If true, the view's endpoint
2736         * inclusion is enough to consider it partially visible. The latter case can then refer to
2737         * an out-of-bounds view positioned right at the top (or bottom) boundaries of RV's padded
2738         * area. This is used e.g. inside
2739         * {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} for
2740         * calculating the next unfocusable child to become visible on the screen.
2741         * @param fromIndex The child position index to start the search from.
2742         * @param toIndex The child position index to end the search at.
2743         * @param completelyVisible True if we have to only consider completely visible views,
2744         *                          false otherwise.
2745         * @param acceptCompletelyVisible True if we can consider both partially or fully visible
2746         *                                views, false, if only a partially visible child should be
2747         *                                returned.
2748         * @param acceptEndPointInclusion If the view's endpoint intersection with RV's padded
2749         *                                bounded area is enough to consider it partially visible,
2750         *                                false otherwise
2751         * @return The adapter position of the first view that's either partially or fully visible.
2752         * {@link RecyclerView#NO_POSITION} if no such view is found.
2753         */
2754        int findOnePartiallyOrCompletelyVisibleChild(int fromIndex, int toIndex,
2755                boolean completelyVisible,
2756                boolean acceptCompletelyVisible,
2757                boolean acceptEndPointInclusion) {
2758            final int start = mPrimaryOrientation.getStartAfterPadding();
2759            final int end = mPrimaryOrientation.getEndAfterPadding();
2760            final int next = toIndex > fromIndex ? 1 : -1;
2761            for (int i = fromIndex; i != toIndex; i += next) {
2762                final View child = mViews.get(i);
2763                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
2764                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
2765                boolean childStartInclusion = acceptEndPointInclusion ? (childStart <= end)
2766                        : (childStart < end);
2767                boolean childEndInclusion = acceptEndPointInclusion ? (childEnd >= start)
2768                        : (childEnd > start);
2769                if (childStartInclusion && childEndInclusion) {
2770                    if (completelyVisible && acceptCompletelyVisible) {
2771                        // the child has to be completely visible to be returned.
2772                        if (childStart >= start && childEnd <= end) {
2773                            return getPosition(child);
2774                        }
2775                    } else if (acceptCompletelyVisible) {
2776                        // can return either a partially or completely visible child.
2777                        return getPosition(child);
2778                    } else if (childStart < start || childEnd > end) {
2779                        // should return a partially visible child if exists and a completely
2780                        // visible child is not acceptable in this case.
2781                        return getPosition(child);
2782                    }
2783                }
2784            }
2785            return RecyclerView.NO_POSITION;
2786        }
2787
2788        int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
2789            return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, completelyVisible,
2790                    true, false);
2791        }
2792
2793        int findOnePartiallyVisibleChild(int fromIndex, int toIndex,
2794                boolean acceptEndPointInclusion) {
2795            return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, false, false,
2796                    acceptEndPointInclusion);
2797        }
2798
2799        /**
2800         * Depending on the layout direction, returns the View that is after the given position.
2801         */
2802        public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) {
2803            View candidate = null;
2804            if (layoutDir == LayoutState.LAYOUT_START) {
2805                final int limit = mViews.size();
2806                for (int i = 0; i < limit; i++) {
2807                    final View view = mViews.get(i);
2808                    if ((mReverseLayout && getPosition(view) <= referenceChildPosition)
2809                            || (!mReverseLayout && getPosition(view) >= referenceChildPosition)) {
2810                        break;
2811                    }
2812                    if (view.hasFocusable()) {
2813                        candidate = view;
2814                    } else {
2815                        break;
2816                    }
2817                }
2818            } else {
2819                for (int i = mViews.size() - 1; i >= 0; i--) {
2820                    final View view = mViews.get(i);
2821                    if ((mReverseLayout && getPosition(view) >= referenceChildPosition)
2822                            || (!mReverseLayout && getPosition(view) <= referenceChildPosition)) {
2823                        break;
2824                    }
2825                    if (view.hasFocusable()) {
2826                        candidate = view;
2827                    } else {
2828                        break;
2829                    }
2830                }
2831            }
2832            return candidate;
2833        }
2834    }
2835
2836    /**
2837     * An array of mappings from adapter position to span.
2838     * This only grows when a write happens and it grows up to the size of the adapter.
2839     */
2840    static class LazySpanLookup {
2841
2842        private static final int MIN_SIZE = 10;
2843        int[] mData;
2844        List<FullSpanItem> mFullSpanItems;
2845
2846
2847        /**
2848         * Invalidates everything after this position, including full span information
2849         */
2850        int forceInvalidateAfter(int position) {
2851            if (mFullSpanItems != null) {
2852                for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2853                    FullSpanItem fsi = mFullSpanItems.get(i);
2854                    if (fsi.mPosition >= position) {
2855                        mFullSpanItems.remove(i);
2856                    }
2857                }
2858            }
2859            return invalidateAfter(position);
2860        }
2861
2862        /**
2863         * returns end position for invalidation.
2864         */
2865        int invalidateAfter(int position) {
2866            if (mData == null) {
2867                return RecyclerView.NO_POSITION;
2868            }
2869            if (position >= mData.length) {
2870                return RecyclerView.NO_POSITION;
2871            }
2872            int endPosition = invalidateFullSpansAfter(position);
2873            if (endPosition == RecyclerView.NO_POSITION) {
2874                Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
2875                return mData.length;
2876            } else {
2877                // just invalidate items in between
2878                Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID);
2879                return endPosition + 1;
2880            }
2881        }
2882
2883        int getSpan(int position) {
2884            if (mData == null || position >= mData.length) {
2885                return LayoutParams.INVALID_SPAN_ID;
2886            } else {
2887                return mData[position];
2888            }
2889        }
2890
2891        void setSpan(int position, Span span) {
2892            ensureSize(position);
2893            mData[position] = span.mIndex;
2894        }
2895
2896        int sizeForPosition(int position) {
2897            int len = mData.length;
2898            while (len <= position) {
2899                len *= 2;
2900            }
2901            return len;
2902        }
2903
2904        void ensureSize(int position) {
2905            if (mData == null) {
2906                mData = new int[Math.max(position, MIN_SIZE) + 1];
2907                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2908            } else if (position >= mData.length) {
2909                int[] old = mData;
2910                mData = new int[sizeForPosition(position)];
2911                System.arraycopy(old, 0, mData, 0, old.length);
2912                Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
2913            }
2914        }
2915
2916        void clear() {
2917            if (mData != null) {
2918                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2919            }
2920            mFullSpanItems = null;
2921        }
2922
2923        void offsetForRemoval(int positionStart, int itemCount) {
2924            if (mData == null || positionStart >= mData.length) {
2925                return;
2926            }
2927            ensureSize(positionStart + itemCount);
2928            System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
2929                    mData.length - positionStart - itemCount);
2930            Arrays.fill(mData, mData.length - itemCount, mData.length,
2931                    LayoutParams.INVALID_SPAN_ID);
2932            offsetFullSpansForRemoval(positionStart, itemCount);
2933        }
2934
2935        private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
2936            if (mFullSpanItems == null) {
2937                return;
2938            }
2939            final int end = positionStart + itemCount;
2940            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2941                FullSpanItem fsi = mFullSpanItems.get(i);
2942                if (fsi.mPosition < positionStart) {
2943                    continue;
2944                }
2945                if (fsi.mPosition < end) {
2946                    mFullSpanItems.remove(i);
2947                } else {
2948                    fsi.mPosition -= itemCount;
2949                }
2950            }
2951        }
2952
2953        void offsetForAddition(int positionStart, int itemCount) {
2954            if (mData == null || positionStart >= mData.length) {
2955                return;
2956            }
2957            ensureSize(positionStart + itemCount);
2958            System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
2959                    mData.length - positionStart - itemCount);
2960            Arrays.fill(mData, positionStart, positionStart + itemCount,
2961                    LayoutParams.INVALID_SPAN_ID);
2962            offsetFullSpansForAddition(positionStart, itemCount);
2963        }
2964
2965        private void offsetFullSpansForAddition(int positionStart, int itemCount) {
2966            if (mFullSpanItems == null) {
2967                return;
2968            }
2969            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2970                FullSpanItem fsi = mFullSpanItems.get(i);
2971                if (fsi.mPosition < positionStart) {
2972                    continue;
2973                }
2974                fsi.mPosition += itemCount;
2975            }
2976        }
2977
2978        /**
2979         * Returns when invalidation should end. e.g. hitting a full span position.
2980         * Returned position SHOULD BE invalidated.
2981         */
2982        private int invalidateFullSpansAfter(int position) {
2983            if (mFullSpanItems == null) {
2984                return RecyclerView.NO_POSITION;
2985            }
2986            final FullSpanItem item = getFullSpanItem(position);
2987            // if there is an fsi at this position, get rid of it.
2988            if (item != null) {
2989                mFullSpanItems.remove(item);
2990            }
2991            int nextFsiIndex = -1;
2992            final int count = mFullSpanItems.size();
2993            for (int i = 0; i < count; i++) {
2994                FullSpanItem fsi = mFullSpanItems.get(i);
2995                if (fsi.mPosition >= position) {
2996                    nextFsiIndex = i;
2997                    break;
2998                }
2999            }
3000            if (nextFsiIndex != -1) {
3001                FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
3002                mFullSpanItems.remove(nextFsiIndex);
3003                return fsi.mPosition;
3004            }
3005            return RecyclerView.NO_POSITION;
3006        }
3007
3008        public void addFullSpanItem(FullSpanItem fullSpanItem) {
3009            if (mFullSpanItems == null) {
3010                mFullSpanItems = new ArrayList<>();
3011            }
3012            final int size = mFullSpanItems.size();
3013            for (int i = 0; i < size; i++) {
3014                FullSpanItem other = mFullSpanItems.get(i);
3015                if (other.mPosition == fullSpanItem.mPosition) {
3016                    if (DEBUG) {
3017                        throw new IllegalStateException("two fsis for same position");
3018                    } else {
3019                        mFullSpanItems.remove(i);
3020                    }
3021                }
3022                if (other.mPosition >= fullSpanItem.mPosition) {
3023                    mFullSpanItems.add(i, fullSpanItem);
3024                    return;
3025                }
3026            }
3027            // if it is not added to a position.
3028            mFullSpanItems.add(fullSpanItem);
3029        }
3030
3031        public FullSpanItem getFullSpanItem(int position) {
3032            if (mFullSpanItems == null) {
3033                return null;
3034            }
3035            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
3036                final FullSpanItem fsi = mFullSpanItems.get(i);
3037                if (fsi.mPosition == position) {
3038                    return fsi;
3039                }
3040            }
3041            return null;
3042        }
3043
3044        /**
3045         * @param minPos inclusive
3046         * @param maxPos exclusive
3047         * @param gapDir if not 0, returns FSIs on in that direction
3048         * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
3049         *                        returned even if its gap direction does not match.
3050         */
3051        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
3052                boolean hasUnwantedGapAfter) {
3053            if (mFullSpanItems == null) {
3054                return null;
3055            }
3056            final int limit = mFullSpanItems.size();
3057            for (int i = 0; i < limit; i++) {
3058                FullSpanItem fsi = mFullSpanItems.get(i);
3059                if (fsi.mPosition >= maxPos) {
3060                    return null;
3061                }
3062                if (fsi.mPosition >= minPos
3063                        && (gapDir == 0 || fsi.mGapDir == gapDir
3064                        || (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
3065                    return fsi;
3066                }
3067            }
3068            return null;
3069        }
3070
3071        /**
3072         * We keep information about full span items because they may create gaps in the UI.
3073         */
3074        static class FullSpanItem implements Parcelable {
3075
3076            int mPosition;
3077            int mGapDir;
3078            int[] mGapPerSpan;
3079            // A full span may be laid out in primary direction but may have gaps due to
3080            // invalidation of views after it. This is recorded during a reverse scroll and if
3081            // view is still on the screen after scroll stops, we have to recalculate layout
3082            boolean mHasUnwantedGapAfter;
3083
3084            FullSpanItem(Parcel in) {
3085                mPosition = in.readInt();
3086                mGapDir = in.readInt();
3087                mHasUnwantedGapAfter = in.readInt() == 1;
3088                int spanCount = in.readInt();
3089                if (spanCount > 0) {
3090                    mGapPerSpan = new int[spanCount];
3091                    in.readIntArray(mGapPerSpan);
3092                }
3093            }
3094
3095            FullSpanItem() {
3096            }
3097
3098            int getGapForSpan(int spanIndex) {
3099                return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
3100            }
3101
3102            @Override
3103            public int describeContents() {
3104                return 0;
3105            }
3106
3107            @Override
3108            public void writeToParcel(Parcel dest, int flags) {
3109                dest.writeInt(mPosition);
3110                dest.writeInt(mGapDir);
3111                dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
3112                if (mGapPerSpan != null && mGapPerSpan.length > 0) {
3113                    dest.writeInt(mGapPerSpan.length);
3114                    dest.writeIntArray(mGapPerSpan);
3115                } else {
3116                    dest.writeInt(0);
3117                }
3118            }
3119
3120            @Override
3121            public String toString() {
3122                return "FullSpanItem{"
3123                        + "mPosition=" + mPosition
3124                        + ", mGapDir=" + mGapDir
3125                        + ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter
3126                        + ", mGapPerSpan=" + Arrays.toString(mGapPerSpan)
3127                        + '}';
3128            }
3129
3130            public static final Parcelable.Creator<FullSpanItem> CREATOR =
3131                    new Parcelable.Creator<FullSpanItem>() {
3132                        @Override
3133                        public FullSpanItem createFromParcel(Parcel in) {
3134                            return new FullSpanItem(in);
3135                        }
3136
3137                        @Override
3138                        public FullSpanItem[] newArray(int size) {
3139                            return new FullSpanItem[size];
3140                        }
3141                    };
3142        }
3143    }
3144
3145    /**
3146     * @hide
3147     */
3148    @RestrictTo(LIBRARY_GROUP)
3149    public static class SavedState implements Parcelable {
3150
3151        int mAnchorPosition;
3152        int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
3153        int mSpanOffsetsSize;
3154        int[] mSpanOffsets;
3155        int mSpanLookupSize;
3156        int[] mSpanLookup;
3157        List<LazySpanLookup.FullSpanItem> mFullSpanItems;
3158        boolean mReverseLayout;
3159        boolean mAnchorLayoutFromEnd;
3160        boolean mLastLayoutRTL;
3161
3162        public SavedState() {
3163        }
3164
3165        SavedState(Parcel in) {
3166            mAnchorPosition = in.readInt();
3167            mVisibleAnchorPosition = in.readInt();
3168            mSpanOffsetsSize = in.readInt();
3169            if (mSpanOffsetsSize > 0) {
3170                mSpanOffsets = new int[mSpanOffsetsSize];
3171                in.readIntArray(mSpanOffsets);
3172            }
3173
3174            mSpanLookupSize = in.readInt();
3175            if (mSpanLookupSize > 0) {
3176                mSpanLookup = new int[mSpanLookupSize];
3177                in.readIntArray(mSpanLookup);
3178            }
3179            mReverseLayout = in.readInt() == 1;
3180            mAnchorLayoutFromEnd = in.readInt() == 1;
3181            mLastLayoutRTL = in.readInt() == 1;
3182            //noinspection unchecked
3183            mFullSpanItems = in.readArrayList(
3184                    LazySpanLookup.FullSpanItem.class.getClassLoader());
3185        }
3186
3187        public SavedState(SavedState other) {
3188            mSpanOffsetsSize = other.mSpanOffsetsSize;
3189            mAnchorPosition = other.mAnchorPosition;
3190            mVisibleAnchorPosition = other.mVisibleAnchorPosition;
3191            mSpanOffsets = other.mSpanOffsets;
3192            mSpanLookupSize = other.mSpanLookupSize;
3193            mSpanLookup = other.mSpanLookup;
3194            mReverseLayout = other.mReverseLayout;
3195            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
3196            mLastLayoutRTL = other.mLastLayoutRTL;
3197            mFullSpanItems = other.mFullSpanItems;
3198        }
3199
3200        void invalidateSpanInfo() {
3201            mSpanOffsets = null;
3202            mSpanOffsetsSize = 0;
3203            mSpanLookupSize = 0;
3204            mSpanLookup = null;
3205            mFullSpanItems = null;
3206        }
3207
3208        void invalidateAnchorPositionInfo() {
3209            mSpanOffsets = null;
3210            mSpanOffsetsSize = 0;
3211            mAnchorPosition = RecyclerView.NO_POSITION;
3212            mVisibleAnchorPosition = RecyclerView.NO_POSITION;
3213        }
3214
3215        @Override
3216        public int describeContents() {
3217            return 0;
3218        }
3219
3220        @Override
3221        public void writeToParcel(Parcel dest, int flags) {
3222            dest.writeInt(mAnchorPosition);
3223            dest.writeInt(mVisibleAnchorPosition);
3224            dest.writeInt(mSpanOffsetsSize);
3225            if (mSpanOffsetsSize > 0) {
3226                dest.writeIntArray(mSpanOffsets);
3227            }
3228            dest.writeInt(mSpanLookupSize);
3229            if (mSpanLookupSize > 0) {
3230                dest.writeIntArray(mSpanLookup);
3231            }
3232            dest.writeInt(mReverseLayout ? 1 : 0);
3233            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
3234            dest.writeInt(mLastLayoutRTL ? 1 : 0);
3235            dest.writeList(mFullSpanItems);
3236        }
3237
3238        public static final Parcelable.Creator<SavedState> CREATOR =
3239                new Parcelable.Creator<SavedState>() {
3240                    @Override
3241                    public SavedState createFromParcel(Parcel in) {
3242                        return new SavedState(in);
3243                    }
3244
3245                    @Override
3246                    public SavedState[] newArray(int size) {
3247                        return new SavedState[size];
3248                    }
3249                };
3250    }
3251
3252    /**
3253     * Data class to hold the information about an anchor position which is used in onLayout call.
3254     */
3255    class AnchorInfo {
3256
3257        int mPosition;
3258        int mOffset;
3259        boolean mLayoutFromEnd;
3260        boolean mInvalidateOffsets;
3261        boolean mValid;
3262        // this is where we save span reference lines in case we need to re-use them for multi-pass
3263        // measure steps
3264        int[] mSpanReferenceLines;
3265
3266        AnchorInfo() {
3267            reset();
3268        }
3269
3270        void reset() {
3271            mPosition = RecyclerView.NO_POSITION;
3272            mOffset = INVALID_OFFSET;
3273            mLayoutFromEnd = false;
3274            mInvalidateOffsets = false;
3275            mValid = false;
3276            if (mSpanReferenceLines != null) {
3277                Arrays.fill(mSpanReferenceLines, -1);
3278            }
3279        }
3280
3281        void saveSpanReferenceLines(Span[] spans) {
3282            int spanCount = spans.length;
3283            if (mSpanReferenceLines == null || mSpanReferenceLines.length < spanCount) {
3284                mSpanReferenceLines = new int[mSpans.length];
3285            }
3286            for (int i = 0; i < spanCount; i++) {
3287                // does not matter start or end since this is only recorded when span is reset
3288                mSpanReferenceLines[i] = spans[i].getStartLine(Span.INVALID_LINE);
3289            }
3290        }
3291
3292        void assignCoordinateFromPadding() {
3293            mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
3294                    : mPrimaryOrientation.getStartAfterPadding();
3295        }
3296
3297        void assignCoordinateFromPadding(int addedDistance) {
3298            if (mLayoutFromEnd) {
3299                mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
3300            } else {
3301                mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;
3302            }
3303        }
3304    }
3305}
3306