StaggeredGridLayoutManager.java revision a910619e83d0052e1d81aa5fe532821a2f99d76c
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget;
18
19import android.content.Context;
20import android.graphics.PointF;
21import android.graphics.Rect;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.support.v4.view.ViewCompat;
25import android.support.v4.view.accessibility.AccessibilityEventCompat;
26import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
27import android.support.v4.view.accessibility.AccessibilityRecordCompat;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.accessibility.AccessibilityEvent;
33
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.BitSet;
37import java.util.List;
38
39import static android.support.v7.widget.LayoutState.LAYOUT_START;
40import static android.support.v7.widget.LayoutState.LAYOUT_END;
41import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
42import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
43import static android.support.v7.widget.RecyclerView.NO_POSITION;
44
45/**
46 * A LayoutManager that lays out children in a staggered grid formation.
47 * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
48 * <p>
49 * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
50 * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
51 * control this behavior via {@link #setGapStrategy(int)}.
52 */
53public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
54
55    public static final String TAG = "StaggeredGridLayoutManager";
56
57    private static final boolean DEBUG = false;
58
59    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
60
61    public static final int VERTICAL = OrientationHelper.VERTICAL;
62
63    /**
64     * Does not do anything to hide gaps.
65     */
66    public static final int GAP_HANDLING_NONE = 0;
67
68    @Deprecated
69    public static final int GAP_HANDLING_LAZY = 1;
70
71    /**
72     * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
73     * check if there are gaps in the because of full span items. If it finds, it will re-layout
74     * and move items to correct positions with animations.
75     * <p>
76     * For example, if LayoutManager ends up with the following layout due to adapter changes:
77     * <code>
78     * AAA
79     * _BC
80     * DDD
81     * </code>
82     * It will animate to the following state:
83     * <code>
84     * AAA
85     * BC_
86     * DDD
87     * </code>
88     */
89    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
90
91    private static final int INVALID_OFFSET = Integer.MIN_VALUE;
92
93    /**
94     * Number of spans
95     */
96    private int mSpanCount = -1;
97
98    private Span[] mSpans;
99
100    /**
101     * Primary orientation is the layout's orientation, secondary orientation is the orientation
102     * for spans. Having both makes code much cleaner for calculations.
103     */
104    OrientationHelper mPrimaryOrientation;
105    OrientationHelper mSecondaryOrientation;
106
107    private int mOrientation;
108
109    /**
110     * The width or height per span, depending on the orientation.
111     */
112    private int mSizePerSpan;
113
114    private LayoutState mLayoutState;
115
116    private boolean mReverseLayout = false;
117
118    /**
119     * Aggregated reverse layout value that takes RTL into account.
120     */
121    boolean mShouldReverseLayout = false;
122
123    /**
124     * Temporary variable used during fill method to check which spans needs to be filled.
125     */
126    private BitSet mRemainingSpans;
127
128    /**
129     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
130     * layout which will check this variable and re-layout accordingly.
131     */
132    int mPendingScrollPosition = NO_POSITION;
133
134    /**
135     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
136     * called.
137     */
138    int mPendingScrollPositionOffset = INVALID_OFFSET;
139
140    /**
141     * Keeps the mapping between the adapter positions and spans. This is necessary to provide
142     * a consistent experience when user scrolls the list.
143     */
144    LazySpanLookup mLazySpanLookup = new LazySpanLookup();
145
146    /**
147     * how we handle gaps in UI.
148     */
149    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
150
151    /**
152     * Saved state needs this information to properly layout on restore.
153     */
154    private boolean mLastLayoutFromEnd;
155
156    /**
157     * Saved state and onLayout needs this information to re-layout properly
158     */
159    private boolean mLastLayoutRTL;
160
161    /**
162     * SavedState is not handled until a layout happens. This is where we keep it until next
163     * layout.
164     */
165    private SavedState mPendingSavedState;
166
167    /**
168     * Re-used measurement specs. updated by onLayout.
169     */
170    private int mFullSizeSpec, mWidthSpec, mHeightSpec;
171
172    /**
173     * Re-used anchor info.
174     */
175    private final AnchorInfo mAnchorInfo = new AnchorInfo();
176
177    /**
178     * If a full span item is invalid / or created in reverse direction; it may create gaps in
179     * the UI. While laying out, if such case is detected, we set this flag.
180     * <p>
181     * After scrolling stops, we check this flag and if it is set, re-layout.
182     */
183    private boolean mLaidOutInvalidFullSpan = false;
184
185    /**
186     * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
187     * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
188     */
189    private boolean mSmoothScrollbarEnabled = true;
190
191    private final Runnable checkForGapsRunnable = new Runnable() {
192        @Override
193        public void run() {
194            checkForGaps();
195        }
196    };
197
198    /**
199     * Creates a StaggeredGridLayoutManager with given parameters.
200     *
201     * @param spanCount   If orientation is vertical, spanCount is number of columns. If
202     *                    orientation is horizontal, spanCount is number of rows.
203     * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
204     */
205    public StaggeredGridLayoutManager(int spanCount, int orientation) {
206        mOrientation = orientation;
207        setSpanCount(spanCount);
208    }
209
210    /**
211     * Checks for gaps in the UI that may be caused by adapter changes.
212     * <p>
213     * When a full span item is laid out in reverse direction, it sets a flag which we check when
214     * scroll is stopped (or re-layout happens) and re-layout after first valid item.
215     */
216    private void checkForGaps() {
217        if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE) {
218            return;
219        }
220        final int minPos, maxPos;
221        if (mShouldReverseLayout) {
222            minPos = getLastChildPosition();
223            maxPos = getFirstChildPosition();
224        } else {
225            minPos = getFirstChildPosition();
226            maxPos = getLastChildPosition();
227        }
228        if (minPos == 0) {
229            View gapView = hasGapsToFix();
230            if (gapView != null) {
231                mLazySpanLookup.clear();
232                requestSimpleAnimationsInNextLayout();
233                requestLayout();
234                return;
235            }
236        }
237        if (!mLaidOutInvalidFullSpan) {
238            return;
239        }
240        int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
241        final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
242                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir);
243        if (invalidFsi == null) {
244            mLaidOutInvalidFullSpan = false;
245            mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
246            return;
247        }
248        final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
249                .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
250                        invalidGapDir * -1);
251        if (validFsi == null) {
252            mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
253        } else {
254            mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
255        }
256        requestSimpleAnimationsInNextLayout();
257        requestLayout();
258    }
259
260    @Override
261    public void onScrollStateChanged(int state) {
262        if (state == RecyclerView.SCROLL_STATE_IDLE) {
263            checkForGaps();
264        }
265    }
266
267    @Override
268    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
269        for (int i = 0; i < mSpanCount; i++) {
270            mSpans[i].clear();
271        }
272    }
273
274    /**
275     * Checks for gaps if we've reached to the top of the list.
276     * <p>
277     * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
278     */
279    View hasGapsToFix() {
280        int startChildIndex = 0;
281        int endChildIndex = getChildCount() - 1;
282        BitSet mSpansToCheck = new BitSet(mSpanCount);
283        mSpansToCheck.set(0, mSpanCount, true);
284
285        final int firstChildIndex, childLimit;
286        final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
287
288        if (mShouldReverseLayout) {
289            firstChildIndex = endChildIndex - 1;
290            childLimit = startChildIndex - 1;
291        } else {
292            firstChildIndex = startChildIndex;
293            childLimit = endChildIndex;
294        }
295        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
296        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
297            View child = getChildAt(i);
298            LayoutParams lp = (LayoutParams) child.getLayoutParams();
299            if (mSpansToCheck.get(lp.mSpan.mIndex)) {
300                if (checkSpanForGap(lp.mSpan)) {
301                    return child;
302                }
303                mSpansToCheck.clear(lp.mSpan.mIndex);
304            }
305            if (lp.mFullSpan) {
306                continue; // quick reject
307            }
308
309            if (i + nextChildDiff != childLimit) {
310                View nextChild = getChildAt(i + nextChildDiff);
311                boolean compareSpans = false;
312                if (mShouldReverseLayout) {
313                    // ensure child's end is below nextChild's end
314                    int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
315                    int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
316                    if (myEnd < nextEnd) {
317                        return child;//i should have a better position
318                    } else if (myEnd == nextEnd) {
319                        compareSpans = true;
320                    }
321                } else {
322                    int myStart = mPrimaryOrientation.getDecoratedStart(child);
323                    int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
324                    if (myStart > nextStart) {
325                        return child;//i should have a better position
326                    } else if (myStart == nextStart) {
327                        compareSpans = true;
328                    }
329                }
330                if (compareSpans) {
331                    // equal, check span indices.
332                    LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
333                    if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
334                        return child;
335                    }
336                }
337            }
338        }
339        // everything looks good
340        return null;
341    }
342
343    private boolean checkSpanForGap(Span span) {
344        if (mShouldReverseLayout) {
345            if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
346                return true;
347            }
348        } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
349            return true;
350        }
351        return false;
352    }
353
354    /**
355     * Sets the number of spans for the layout. This will invalidate all of the span assignments
356     * for Views.
357     * <p>
358     * Calling this method will automatically result in a new layout request unless the spanCount
359     * parameter is equal to current span count.
360     *
361     * @param spanCount Number of spans to layout
362     */
363    public void setSpanCount(int spanCount) {
364        assertNotInLayoutOrScroll(null);
365        if (spanCount != mSpanCount) {
366            invalidateSpanAssignments();
367            mSpanCount = spanCount;
368            mRemainingSpans = new BitSet(mSpanCount);
369            mSpans = new Span[mSpanCount];
370            for (int i = 0; i < mSpanCount; i++) {
371                mSpans[i] = new Span(i);
372            }
373            requestLayout();
374        }
375    }
376
377    /**
378     * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
379     * scroll position if this method is called after views are laid out.
380     *
381     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
382     */
383    public void setOrientation(int orientation) {
384        if (orientation != HORIZONTAL && orientation != VERTICAL) {
385            throw new IllegalArgumentException("invalid orientation.");
386        }
387        assertNotInLayoutOrScroll(null);
388        if (orientation == mOrientation) {
389            return;
390        }
391        mOrientation = orientation;
392        if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
393            // swap
394            OrientationHelper tmp = mPrimaryOrientation;
395            mPrimaryOrientation = mSecondaryOrientation;
396            mSecondaryOrientation = tmp;
397        }
398        requestLayout();
399    }
400
401    /**
402     * Sets whether LayoutManager should start laying out items from the end of the UI. The order
403     * items are traversed is not affected by this call.
404     * <p>
405     * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
406     * the list.
407     * <p>
408     * For horizontal layouts, it depends on the layout direction.
409     * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
410     * {@link RecyclerView}} is RTL, it will layout from LTR.
411     *
412     * @param reverseLayout Whether layout should be in reverse or not
413     */
414    public void setReverseLayout(boolean reverseLayout) {
415        assertNotInLayoutOrScroll(null);
416        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
417            mPendingSavedState.mReverseLayout = reverseLayout;
418        }
419        mReverseLayout = reverseLayout;
420        requestLayout();
421    }
422
423    /**
424     * Returns the current gap handling strategy for StaggeredGridLayoutManager.
425     * <p>
426     * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
427     * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
428     * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
429     * <p>
430     * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
431     *
432     * @return Current gap handling strategy.
433     * @see #setGapStrategy(int)
434     * @see #GAP_HANDLING_NONE
435     * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
436     */
437    public int getGapStrategy() {
438        return mGapStrategy;
439    }
440
441    /**
442     * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
443     * is different than the current strategy, calling this method will trigger a layout request.
444     *
445     * @param gapStrategy The new gap handling strategy. Should be
446     *                    {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
447     *                    #GAP_HANDLING_NONE}.
448     * @see #getGapStrategy()
449     */
450    public void setGapStrategy(int gapStrategy) {
451        assertNotInLayoutOrScroll(null);
452        if (gapStrategy == mGapStrategy) {
453            return;
454        }
455        if (gapStrategy != GAP_HANDLING_NONE &&
456                gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
457            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
458                    + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
459        }
460        mGapStrategy = gapStrategy;
461        requestLayout();
462    }
463
464    @Override
465    public void assertNotInLayoutOrScroll(String message) {
466        if (mPendingSavedState == null) {
467            super.assertNotInLayoutOrScroll(message);
468        }
469    }
470
471    /**
472     * Returns the number of spans laid out by StaggeredGridLayoutManager.
473     *
474     * @return Number of spans in the layout
475     */
476    public int getSpanCount() {
477        return mSpanCount;
478    }
479
480    /**
481     * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
482     * <p>
483     * If you need to cancel current assignments, you can call this method which will clear all
484     * assignments and request a new layout.
485     */
486    public void invalidateSpanAssignments() {
487        mLazySpanLookup.clear();
488        requestLayout();
489    }
490
491    private void ensureOrientationHelper() {
492        if (mPrimaryOrientation == null) {
493            mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
494            mSecondaryOrientation = OrientationHelper
495                    .createOrientationHelper(this, 1 - mOrientation);
496            mLayoutState = new LayoutState();
497        }
498    }
499
500    /**
501     * Calculates the views' layout order. (e.g. from end to start or start to end)
502     * RTL layout support is applied automatically. So if layout is RTL and
503     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
504     */
505    private void resolveShouldLayoutReverse() {
506        // A == B is the same result, but we rather keep it readable
507        if (mOrientation == VERTICAL || !isLayoutRTL()) {
508            mShouldReverseLayout = mReverseLayout;
509        } else {
510            mShouldReverseLayout = !mReverseLayout;
511        }
512    }
513
514    boolean isLayoutRTL() {
515        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
516    }
517
518    /**
519     * Returns whether views are laid out in reverse order or not.
520     * <p>
521     * Not that this value is not affected by RecyclerView's layout direction.
522     *
523     * @return True if layout is reversed, false otherwise
524     * @see #setReverseLayout(boolean)
525     */
526    public boolean getReverseLayout() {
527        return mReverseLayout;
528    }
529
530    @Override
531    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
532        ensureOrientationHelper();
533        // Update adapter size.
534        mLazySpanLookup.mAdapterSize = state.getItemCount();
535
536        final AnchorInfo anchorInfo = mAnchorInfo;
537        anchorInfo.reset();
538
539        if (mPendingSavedState != null) {
540            applyPendingSavedState(anchorInfo);
541        } else {
542            resolveShouldLayoutReverse();
543            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
544        }
545
546        updateAnchorInfoForLayout(state, anchorInfo);
547
548        if (mPendingSavedState == null) {
549            if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
550                    isLayoutRTL() != mLastLayoutRTL) {
551                mLazySpanLookup.clear();
552                anchorInfo.mInvalidateOffsets = true;
553            }
554        }
555
556        if (getChildCount() > 0 && (mPendingSavedState == null ||
557                mPendingSavedState.mSpanOffsetsSize < 1)) {
558            if (anchorInfo.mInvalidateOffsets) {
559                for (int i = 0; i < mSpanCount; i++) {
560                    // Scroll to position is set, clear.
561                    mSpans[i].clear();
562                    if (anchorInfo.mOffset != INVALID_OFFSET) {
563                        mSpans[i].setLine(anchorInfo.mOffset);
564                    }
565                }
566            } else {
567                for (int i = 0; i < mSpanCount; i++) {
568                    mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset);
569                }
570            }
571        }
572        detachAndScrapAttachedViews(recycler);
573        mLaidOutInvalidFullSpan = false;
574        updateMeasureSpecs();
575        if (anchorInfo.mLayoutFromEnd) {
576            // Layout start.
577            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
578            fill(recycler, mLayoutState, state);
579            // Layout end.
580            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
581            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
582            fill(recycler, mLayoutState, state);
583        } else {
584            // Layout end.
585            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
586            fill(recycler, mLayoutState, state);
587            // Layout start.
588            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
589            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
590            fill(recycler, mLayoutState, state);
591        }
592
593        if (getChildCount() > 0) {
594            if (mShouldReverseLayout) {
595                fixEndGap(recycler, state, true);
596                fixStartGap(recycler, state, false);
597            } else {
598                fixStartGap(recycler, state, true);
599                fixEndGap(recycler, state, false);
600            }
601        }
602
603        if (!state.isPreLayout()) {
604            if (getChildCount() > 0 && mPendingScrollPosition != NO_POSITION &&
605                    mLaidOutInvalidFullSpan) {
606                ViewCompat.postOnAnimation(getChildAt(0), checkForGapsRunnable);
607            }
608            mPendingScrollPosition = NO_POSITION;
609            mPendingScrollPositionOffset = INVALID_OFFSET;
610        }
611        mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
612        mLastLayoutRTL = isLayoutRTL();
613        mPendingSavedState = null; // we don't need this anymore
614    }
615
616    private void applyPendingSavedState(AnchorInfo anchorInfo) {
617        if (DEBUG) {
618            Log.d(TAG, "found saved state: " + mPendingSavedState);
619        }
620        if (mPendingSavedState.mSpanOffsetsSize > 0) {
621            if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
622                for (int i = 0; i < mSpanCount; i++) {
623                    mSpans[i].clear();
624                    int line = mPendingSavedState.mSpanOffsets[i];
625                    if (line != Span.INVALID_LINE) {
626                        if (mPendingSavedState.mAnchorLayoutFromEnd) {
627                            line += mPrimaryOrientation.getEndAfterPadding();
628                        } else {
629                            line += mPrimaryOrientation.getStartAfterPadding();
630                        }
631                    }
632                    mSpans[i].setLine(line);
633                }
634            } else {
635                mPendingSavedState.invalidateSpanInfo();
636                mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
637            }
638        }
639        mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
640        setReverseLayout(mPendingSavedState.mReverseLayout);
641        resolveShouldLayoutReverse();
642
643        if (mPendingSavedState.mAnchorPosition != NO_POSITION) {
644            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
645            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
646        } else {
647            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
648        }
649        if (mPendingSavedState.mSpanLookupSize > 1) {
650            mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
651            mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
652        }
653    }
654
655    void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
656        if (updateAnchorFromPendingData(state, anchorInfo)) {
657            return;
658        }
659        if (updateAnchorFromChildren(state, anchorInfo)) {
660            return;
661        }
662        if (DEBUG) {
663            Log.d(TAG, "Deciding anchor info from fresh state");
664        }
665        anchorInfo.assignCoordinateFromPadding();
666        anchorInfo.mPosition = 0;
667    }
668
669    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
670        // We don't recycle views out of adapter order. This way, we can rely on the first or
671        // last child as the anchor position.
672        // Layout direction may change but we should select the child depending on the latest
673        // layout direction. Otherwise, we'll choose the wrong child.
674        anchorInfo.mPosition = mLastLayoutFromEnd
675                ? findLastReferenceChildPosition(state.getItemCount())
676                : findFirstReferenceChildPosition(state.getItemCount());
677        anchorInfo.mOffset = INVALID_OFFSET;
678        return true;
679    }
680
681    boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
682        // Validate scroll position if exists.
683        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
684            return false;
685        }
686        // Validate it.
687        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
688            mPendingScrollPosition = NO_POSITION;
689            mPendingScrollPositionOffset = INVALID_OFFSET;
690            return false;
691        }
692
693        if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION
694                || mPendingSavedState.mSpanOffsetsSize < 1) {
695            // If item is visible, make it fully visible.
696            final View child = findViewByPosition(mPendingScrollPosition);
697            if (child != null) {
698                // Use regular anchor position, offset according to pending offset and target
699                // child
700                anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
701                        : getFirstChildPosition();
702
703                if (mPendingScrollPositionOffset != INVALID_OFFSET) {
704                    if (anchorInfo.mLayoutFromEnd) {
705                        final int target = mPrimaryOrientation.getEndAfterPadding() -
706                                mPendingScrollPositionOffset;
707                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
708                    } else {
709                        final int target = mPrimaryOrientation.getStartAfterPadding() +
710                                mPendingScrollPositionOffset;
711                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
712                    }
713                    return true;
714                }
715
716                // no offset provided. Decide according to the child location
717                final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
718                if (childSize > mPrimaryOrientation.getTotalSpace()) {
719                    // Item does not fit. Fix depending on layout direction.
720                    anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
721                            ? mPrimaryOrientation.getEndAfterPadding()
722                            : mPrimaryOrientation.getStartAfterPadding();
723                    return true;
724                }
725
726                final int startGap = mPrimaryOrientation.getDecoratedStart(child)
727                        - mPrimaryOrientation.getStartAfterPadding();
728                if (startGap < 0) {
729                    anchorInfo.mOffset = -startGap;
730                    return true;
731                }
732                final int endGap = mPrimaryOrientation.getEndAfterPadding() -
733                        mPrimaryOrientation.getDecoratedEnd(child);
734                if (endGap < 0) {
735                    anchorInfo.mOffset = endGap;
736                    return true;
737                }
738                // child already visible. just layout as usual
739                anchorInfo.mOffset = INVALID_OFFSET;
740            } else {
741                // Child is not visible. Set anchor coordinate depending on in which direction
742                // child will be visible.
743                anchorInfo.mPosition = mPendingScrollPosition;
744                if (mPendingScrollPositionOffset == INVALID_OFFSET) {
745                    final int position = calculateScrollDirectionForPosition(
746                            anchorInfo.mPosition);
747                    anchorInfo.mLayoutFromEnd = position == LAYOUT_END;
748                    anchorInfo.assignCoordinateFromPadding();
749                } else {
750                    anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
751                }
752                anchorInfo.mInvalidateOffsets = true;
753            }
754        } else {
755            anchorInfo.mOffset = INVALID_OFFSET;
756            anchorInfo.mPosition = mPendingScrollPosition;
757        }
758        return true;
759    }
760
761    void updateMeasureSpecs() {
762        mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
763        mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
764                mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
765        if (mOrientation == VERTICAL) {
766            mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
767            mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
768        } else {
769            mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
770            mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
771        }
772    }
773
774    @Override
775    public boolean supportsPredictiveItemAnimations() {
776        return mPendingSavedState == null;
777    }
778
779    /**
780     * Returns the adapter position of the first visible view for each span.
781     * <p>
782     * Note that, this value is not affected by layout orientation or item order traversal.
783     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
784     * not in the layout.
785     * <p>
786     * If RecyclerView has item decorators, they will be considered in calculations as well.
787     * <p>
788     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
789     * views are ignored in this method.
790     *
791     * @param into An array to put the results into. If you don't provide any, LayoutManager will
792     *             create a new one.
793     * @return The adapter position of the first visible item in each span. If a span does not have
794     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
795     * @see #findFirstCompletelyVisibleItemPositions(int[])
796     * @see #findLastVisibleItemPositions(int[])
797     */
798    public int[] findFirstVisibleItemPositions(int[] into) {
799        if (into == null) {
800            into = new int[mSpanCount];
801        } else if (into.length < mSpanCount) {
802            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
803                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
804        }
805        for (int i = 0; i < mSpanCount; i++) {
806            into[i] = mSpans[i].findFirstVisibleItemPosition();
807        }
808        return into;
809    }
810
811    /**
812     * Returns the adapter position of the first completely visible view for each span.
813     * <p>
814     * Note that, this value is not affected by layout orientation or item order traversal.
815     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
816     * not in the layout.
817     * <p>
818     * If RecyclerView has item decorators, they will be considered in calculations as well.
819     * <p>
820     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
821     * views are ignored in this method.
822     *
823     * @param into An array to put the results into. If you don't provide any, LayoutManager will
824     *             create a new one.
825     * @return The adapter position of the first fully visible item in each span. If a span does
826     * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
827     * @see #findFirstVisibleItemPositions(int[])
828     * @see #findLastCompletelyVisibleItemPositions(int[])
829     */
830    public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
831        if (into == null) {
832            into = new int[mSpanCount];
833        } else if (into.length < mSpanCount) {
834            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
835                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
836        }
837        for (int i = 0; i < mSpanCount; i++) {
838            into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
839        }
840        return into;
841    }
842
843    /**
844     * Returns the adapter position of the last visible view for each span.
845     * <p>
846     * Note that, this value is not affected by layout orientation or item order traversal.
847     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
848     * not in the layout.
849     * <p>
850     * If RecyclerView has item decorators, they will be considered in calculations as well.
851     * <p>
852     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
853     * views are ignored in this method.
854     *
855     * @param into An array to put the results into. If you don't provide any, LayoutManager will
856     *             create a new one.
857     * @return The adapter position of the last visible item in each span. If a span does not have
858     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
859     * @see #findLastCompletelyVisibleItemPositions(int[])
860     * @see #findFirstVisibleItemPositions(int[])
861     */
862    public int[] findLastVisibleItemPositions(int[] into) {
863        if (into == null) {
864            into = new int[mSpanCount];
865        } else if (into.length < mSpanCount) {
866            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
867                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
868        }
869        for (int i = 0; i < mSpanCount; i++) {
870            into[i] = mSpans[i].findLastVisibleItemPosition();
871        }
872        return into;
873    }
874
875    /**
876     * Returns the adapter position of the last completely visible view for each span.
877     * <p>
878     * Note that, this value is not affected by layout orientation or item order traversal.
879     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
880     * not in the layout.
881     * <p>
882     * If RecyclerView has item decorators, they will be considered in calculations as well.
883     * <p>
884     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
885     * views are ignored in this method.
886     *
887     * @param into An array to put the results into. If you don't provide any, LayoutManager will
888     *             create a new one.
889     * @return The adapter position of the last fully visible item in each span. If a span does not
890     * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
891     * @see #findFirstCompletelyVisibleItemPositions(int[])
892     * @see #findLastVisibleItemPositions(int[])
893     */
894    public int[] findLastCompletelyVisibleItemPositions(int[] into) {
895        if (into == null) {
896            into = new int[mSpanCount];
897        } else if (into.length < mSpanCount) {
898            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
899                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
900        }
901        for (int i = 0; i < mSpanCount; i++) {
902            into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
903        }
904        return into;
905    }
906
907    @Override
908    public int computeHorizontalScrollOffset(RecyclerView.State state) {
909        return computeScrollOffset(state);
910    }
911
912    private int computeScrollOffset(RecyclerView.State state) {
913        if (getChildCount() == 0) {
914            return 0;
915        }
916        return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
917                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
918                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
919                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
920    }
921
922    @Override
923    public int computeVerticalScrollOffset(RecyclerView.State state) {
924        return computeScrollOffset(state);
925    }
926
927    @Override
928    public int computeHorizontalScrollExtent(RecyclerView.State state) {
929        return computeScrollExtent(state);
930    }
931
932    private int computeScrollExtent(RecyclerView.State state) {
933        if (getChildCount() == 0) {
934            return 0;
935        }
936        return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
937                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
938                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
939                this, mSmoothScrollbarEnabled);
940    }
941
942    @Override
943    public int computeVerticalScrollExtent(RecyclerView.State state) {
944        return computeScrollExtent(state);
945    }
946
947    @Override
948    public int computeHorizontalScrollRange(RecyclerView.State state) {
949        return computeScrollRange(state);
950    }
951
952    private int computeScrollRange(RecyclerView.State state) {
953        if (getChildCount() == 0) {
954            return 0;
955        }
956        return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
957                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
958                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
959                this, mSmoothScrollbarEnabled);
960    }
961
962    @Override
963    public int computeVerticalScrollRange(RecyclerView.State state) {
964        return computeScrollRange(state);
965    }
966
967    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
968        if (lp.mFullSpan) {
969            if (mOrientation == VERTICAL) {
970                measureChildWithDecorationsAndMargin(child, mFullSizeSpec, mHeightSpec);
971            } else {
972                measureChildWithDecorationsAndMargin(child, mWidthSpec, mFullSizeSpec);
973            }
974        } else {
975            measureChildWithDecorationsAndMargin(child, mWidthSpec, mHeightSpec);
976        }
977    }
978
979    private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
980            int heightSpec) {
981        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
982        LayoutParams lp = (LayoutParams) child.getLayoutParams();
983        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + insets.left,
984                lp.rightMargin + insets.right);
985        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + insets.top,
986                lp.bottomMargin + insets.bottom);
987        child.measure(widthSpec, heightSpec);
988    }
989
990    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
991        if (startInset == 0 && endInset == 0) {
992            return spec;
993        }
994        final int mode = View.MeasureSpec.getMode(spec);
995        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
996            return View.MeasureSpec.makeMeasureSpec(
997                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
998        }
999        return spec;
1000    }
1001
1002    @Override
1003    public void onRestoreInstanceState(Parcelable state) {
1004        if (state instanceof SavedState) {
1005            mPendingSavedState = (SavedState) state;
1006            requestLayout();
1007        } else if (DEBUG) {
1008            Log.d(TAG, "invalid saved state class");
1009        }
1010    }
1011
1012    @Override
1013    public Parcelable onSaveInstanceState() {
1014        if (mPendingSavedState != null) {
1015            return new SavedState(mPendingSavedState);
1016        }
1017        SavedState state = new SavedState();
1018        state.mReverseLayout = mReverseLayout;
1019        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
1020        state.mLastLayoutRTL = mLastLayoutRTL;
1021
1022        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
1023            state.mSpanLookup = mLazySpanLookup.mData;
1024            state.mSpanLookupSize = state.mSpanLookup.length;
1025            state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
1026        } else {
1027            state.mSpanLookupSize = 0;
1028        }
1029
1030        if (getChildCount() > 0) {
1031            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
1032                    : getFirstChildPosition();
1033            state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
1034            state.mSpanOffsetsSize = mSpanCount;
1035            state.mSpanOffsets = new int[mSpanCount];
1036            for (int i = 0; i < mSpanCount; i++) {
1037                int line;
1038                if (mLastLayoutFromEnd) {
1039                    line = mSpans[i].getEndLine(Span.INVALID_LINE);
1040                    if (line != Span.INVALID_LINE) {
1041                        line -= mPrimaryOrientation.getEndAfterPadding();
1042                    }
1043                } else {
1044                    line = mSpans[i].getStartLine(Span.INVALID_LINE);
1045                    if (line != Span.INVALID_LINE) {
1046                        line -= mPrimaryOrientation.getStartAfterPadding();
1047                    }
1048                }
1049                state.mSpanOffsets[i] = line;
1050            }
1051        } else {
1052            state.mAnchorPosition = NO_POSITION;
1053            state.mVisibleAnchorPosition = NO_POSITION;
1054            state.mSpanOffsetsSize = 0;
1055        }
1056        if (DEBUG) {
1057            Log.d(TAG, "saved state:\n" + state);
1058        }
1059        return state;
1060    }
1061
1062    @Override
1063    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
1064            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
1065        ViewGroup.LayoutParams lp = host.getLayoutParams();
1066        if (!(lp instanceof LayoutParams)) {
1067            super.onInitializeAccessibilityNodeInfoForItem(host, info);
1068            return;
1069        }
1070        LayoutParams sglp = (LayoutParams) lp;
1071        if (mOrientation == HORIZONTAL) {
1072            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1073                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1074                    -1, -1,
1075                    sglp.mFullSpan, false));
1076        } else { // VERTICAL
1077            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1078                    -1, -1,
1079                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1080                    sglp.mFullSpan, false));
1081        }
1082    }
1083
1084    @Override
1085    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1086        super.onInitializeAccessibilityEvent(event);
1087        if (getChildCount() > 0) {
1088            final AccessibilityRecordCompat record = AccessibilityEventCompat
1089                    .asRecord(event);
1090            final View start = findFirstVisibleItemClosestToStart(false);
1091            final View end = findFirstVisibleItemClosestToEnd(false);
1092            if (start == null || end == null) {
1093                return;
1094            }
1095            final int startPos = getPosition(start);
1096            final int endPos = getPosition(end);
1097            if (startPos < endPos) {
1098                record.setFromIndex(startPos);
1099                record.setToIndex(endPos);
1100            } else {
1101                record.setFromIndex(endPos);
1102                record.setToIndex(startPos);
1103            }
1104        }
1105    }
1106
1107    /**
1108     * Finds the first fully visible child to be used as an anchor child if span count changes when
1109     * state is restored.
1110     */
1111    int findFirstVisibleItemPositionInt() {
1112        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
1113                findFirstVisibleItemClosestToStart(true);
1114        return first == null ? NO_POSITION : getPosition(first);
1115    }
1116
1117    @Override
1118    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
1119            RecyclerView.State state) {
1120        if (mOrientation == HORIZONTAL) {
1121            return mSpanCount;
1122        }
1123        return super.getRowCountForAccessibility(recycler, state);
1124    }
1125
1126    @Override
1127    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
1128            RecyclerView.State state) {
1129        if (mOrientation == VERTICAL) {
1130            return mSpanCount;
1131        }
1132        return super.getColumnCountForAccessibility(recycler, state);
1133    }
1134
1135    View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
1136        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1137        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1138        final int limit = getChildCount();
1139        for (int i = 0; i < limit; i ++) {
1140            final View child = getChildAt(i);
1141            if ((!fullyVisible || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)
1142                    && mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
1143                return child;
1144            }
1145        }
1146        return null;
1147    }
1148
1149    View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
1150        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1151        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1152        for (int i = getChildCount() - 1; i >= 0; i --) {
1153            final View child = getChildAt(i);
1154            if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart && (!fullyVisible
1155                    || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd)) {
1156                return child;
1157            }
1158        }
1159        return null;
1160    }
1161
1162    private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1163            boolean canOffsetChildren) {
1164        final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1165        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
1166        int fixOffset;
1167        if (gap > 0) {
1168            fixOffset = -scrollBy(-gap, recycler, state);
1169        } else {
1170            return; // nothing to fix
1171        }
1172        gap -= fixOffset;
1173        if (canOffsetChildren && gap > 0) {
1174            mPrimaryOrientation.offsetChildren(gap);
1175        }
1176    }
1177
1178    private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1179            boolean canOffsetChildren) {
1180        final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1181        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
1182        int fixOffset;
1183        if (gap > 0) {
1184            fixOffset = scrollBy(gap, recycler, state);
1185        } else {
1186            return; // nothing to fix
1187        }
1188        gap -= fixOffset;
1189        if (canOffsetChildren && gap > 0) {
1190            mPrimaryOrientation.offsetChildren(-gap);
1191        }
1192    }
1193
1194    private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) {
1195        mLayoutState.mAvailable = 0;
1196        mLayoutState.mCurrentPosition = anchorPosition;
1197        if (isSmoothScrolling()) {
1198            final int targetPos = state.getTargetScrollPosition();
1199            if (mShouldReverseLayout == targetPos < anchorPosition) {
1200                mLayoutState.mExtra = 0;
1201            } else {
1202                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
1203            }
1204        } else {
1205            mLayoutState.mExtra = 0;
1206        }
1207        mLayoutState.mLayoutDirection = LAYOUT_START;
1208        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
1209                : ITEM_DIRECTION_HEAD;
1210    }
1211
1212    private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) {
1213        mLayoutState.mAvailable = 0;
1214        mLayoutState.mCurrentPosition = anchorPosition;
1215        if (isSmoothScrolling()) {
1216            final int targetPos = state.getTargetScrollPosition();
1217            if (mShouldReverseLayout == targetPos > anchorPosition) {
1218                mLayoutState.mExtra = 0;
1219            } else {
1220                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
1221            }
1222        } else {
1223            mLayoutState.mExtra = 0;
1224        }
1225        mLayoutState.mLayoutDirection = LAYOUT_END;
1226        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
1227                : ITEM_DIRECTION_TAIL;
1228    }
1229
1230    @Override
1231    public void offsetChildrenHorizontal(int dx) {
1232        super.offsetChildrenHorizontal(dx);
1233        for (int i = 0; i < mSpanCount; i++) {
1234            mSpans[i].onOffset(dx);
1235        }
1236    }
1237
1238    @Override
1239    public void offsetChildrenVertical(int dy) {
1240        super.offsetChildrenVertical(dy);
1241        for (int i = 0; i < mSpanCount; i++) {
1242            mSpans[i].onOffset(dy);
1243        }
1244    }
1245
1246    @Override
1247    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
1248        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
1249    }
1250
1251    @Override
1252    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
1253        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
1254    }
1255
1256    @Override
1257    public void onItemsChanged(RecyclerView recyclerView) {
1258        mLazySpanLookup.clear();
1259        requestLayout();
1260    }
1261
1262    @Override
1263    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
1264        handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
1265    }
1266
1267    @Override
1268    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
1269        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
1270    }
1271
1272    /**
1273     * Checks whether it should invalidate span assignments in response to an adapter change.
1274     */
1275    private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
1276        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
1277        mLazySpanLookup.invalidateAfter(positionStart);
1278        switch (cmd) {
1279            case AdapterHelper.UpdateOp.ADD:
1280                mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
1281                break;
1282            case AdapterHelper.UpdateOp.REMOVE:
1283                mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
1284                break;
1285            case AdapterHelper.UpdateOp.MOVE:
1286                // TODO optimize
1287                mLazySpanLookup.offsetForRemoval(positionStart, 1);
1288                mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
1289                break;
1290        }
1291
1292        if (positionStart + itemCountOrToPosition <= minPosition) {
1293            return;
1294
1295        }
1296        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
1297        if (positionStart <= maxPosition) {
1298            requestLayout();
1299        }
1300    }
1301
1302    private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
1303            RecyclerView.State state) {
1304        mRemainingSpans.set(0, mSpanCount, true);
1305        // The target position we are trying to reach.
1306        final int targetLine;
1307        /*
1308        * The line until which we can recycle, as long as we add views.
1309        * Keep in mind, it is still the line in layout direction which means; to calculate the
1310        * actual recycle line, we should subtract/add the size in orientation.
1311        */
1312        final int recycleLine;
1313        // Line of the furthest row.
1314        if (layoutState.mLayoutDirection == LAYOUT_END) {
1315            // ignore padding for recycler
1316            recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
1317            targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();
1318
1319        } else { // LAYOUT_START
1320            // ignore padding for recycler
1321            recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
1322            targetLine = recycleLine - mLayoutState.mExtra -
1323                    mPrimaryOrientation.getStartAfterPadding();
1324        }
1325        updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
1326
1327        // the default coordinate to add new view.
1328        final int defaultNewViewLine = mShouldReverseLayout
1329                ? mPrimaryOrientation.getEndAfterPadding()
1330                : mPrimaryOrientation.getStartAfterPadding();
1331
1332        while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
1333            View view = layoutState.next(recycler);
1334            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
1335            if (layoutState.mLayoutDirection == LAYOUT_END) {
1336                addView(view);
1337            } else {
1338                addView(view, 0);
1339            }
1340            measureChildWithDecorationsAndMargin(view, lp);
1341
1342            final int position = lp.getViewPosition();
1343            final int spanIndex = mLazySpanLookup.getSpan(position);
1344            Span currentSpan;
1345            boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
1346            if (assignSpan) {
1347                currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
1348                mLazySpanLookup.setSpan(position, currentSpan);
1349                if (DEBUG) {
1350                    Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
1351                }
1352            } else {
1353                if (DEBUG) {
1354                    Log.d(TAG, "using " + spanIndex + " for pos " + position);
1355                }
1356                currentSpan = mSpans[spanIndex];
1357            }
1358            final int start;
1359            final int end;
1360
1361            if (layoutState.mLayoutDirection == LAYOUT_END) {
1362                start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
1363                        : currentSpan.getEndLine(defaultNewViewLine);
1364                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
1365                if (assignSpan && lp.mFullSpan) {
1366                    LazySpanLookup.FullSpanItem fullSpanItem;
1367                    fullSpanItem = createFullSpanItemFromEnd(start);
1368                    fullSpanItem.mGapDir = LAYOUT_START;
1369                    fullSpanItem.mPosition = position;
1370                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1371                }
1372            } else {
1373                end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
1374                        : currentSpan.getStartLine(defaultNewViewLine);
1375                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
1376                if (assignSpan && lp.mFullSpan) {
1377                    LazySpanLookup.FullSpanItem fullSpanItem;
1378                    fullSpanItem = createFullSpanItemFromStart(end);
1379                    fullSpanItem.mGapDir = LAYOUT_END;
1380                    fullSpanItem.mPosition = position;
1381                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1382                }
1383            }
1384
1385            // check if this item may create gaps in the future
1386            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD && assignSpan) {
1387                mLaidOutInvalidFullSpan = true;
1388            }
1389
1390            lp.mSpan = currentSpan;
1391            attachViewToSpans(view, lp, layoutState);
1392            final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
1393                    : currentSpan.mIndex * mSizePerSpan +
1394                            mSecondaryOrientation.getStartAfterPadding();
1395            final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
1396            if (mOrientation == VERTICAL) {
1397                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
1398            } else {
1399                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
1400            }
1401
1402            if (lp.mFullSpan) {
1403                updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
1404            } else {
1405                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
1406            }
1407            recycle(recycler, mLayoutState, currentSpan, recycleLine);
1408        }
1409        if (DEBUG) {
1410            Log.d(TAG, "fill, " + getChildCount());
1411        }
1412        if (mLayoutState.mLayoutDirection == LAYOUT_START) {
1413            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1414            return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart));
1415        } else {
1416            final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1417            return Math.max(0, mLayoutState.mAvailable + (max - recycleLine));
1418        }
1419    }
1420
1421    private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
1422        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1423        fsi.mGapPerSpan = new int[mSpanCount];
1424        for (int i = 0; i < mSpanCount; i++) {
1425            fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
1426        }
1427        return fsi;
1428    }
1429
1430    private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
1431        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1432        fsi.mGapPerSpan = new int[mSpanCount];
1433        for (int i = 0; i < mSpanCount; i++) {
1434            fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
1435        }
1436        return fsi;
1437    }
1438
1439    private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) {
1440        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1441            if (lp.mFullSpan) {
1442                appendViewToAllSpans(view);
1443            } else {
1444                lp.mSpan.appendToSpan(view);
1445            }
1446        } else {
1447            if (lp.mFullSpan) {
1448                prependViewToAllSpans(view);
1449            } else {
1450                lp.mSpan.prependToSpan(view);
1451            }
1452        }
1453    }
1454
1455    private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState,
1456            Span updatedSpan, int recycleLine) {
1457        if (layoutState.mLayoutDirection == LAYOUT_START) {
1458            // calculate recycle line
1459            int maxStart = getMaxStart(updatedSpan.getStartLine());
1460            recycleFromEnd(recycler, Math.max(recycleLine, maxStart) +
1461                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
1462        } else {
1463            // calculate recycle line
1464            int minEnd = getMinEnd(updatedSpan.getEndLine());
1465            recycleFromStart(recycler, Math.min(recycleLine, minEnd) -
1466                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
1467        }
1468    }
1469
1470    private void appendViewToAllSpans(View view) {
1471        // traverse in reverse so that we end up assigning full span items to 0
1472        for (int i = mSpanCount - 1; i >= 0; i--) {
1473            mSpans[i].appendToSpan(view);
1474        }
1475    }
1476
1477    private void prependViewToAllSpans(View view) {
1478        // traverse in reverse so that we end up assigning full span items to 0
1479        for (int i = mSpanCount - 1; i >= 0; i--) {
1480            mSpans[i].prependToSpan(view);
1481        }
1482    }
1483
1484    private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
1485        LayoutParams lp = (LayoutParams) child.getLayoutParams();
1486        if (DEBUG) {
1487            Log.d(TAG, "layout decorated pos: " + lp.getViewPosition() + ", span:"
1488                    + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
1489                    + ". l:" + left + ",t:" + top
1490                    + ", r:" + right + ", b:" + bottom);
1491        }
1492        layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
1493                , bottom - lp.bottomMargin);
1494    }
1495
1496    private void updateAllRemainingSpans(int layoutDir, int targetLine) {
1497        for (int i = 0; i < mSpanCount; i++) {
1498            if (mSpans[i].mViews.isEmpty()) {
1499                continue;
1500            }
1501            updateRemainingSpans(mSpans[i], layoutDir, targetLine);
1502        }
1503    }
1504
1505    private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
1506        final int deletedSize = span.getDeletedSize();
1507        if (layoutDir == LAYOUT_START) {
1508            final int line = span.getStartLine();
1509            if (line + deletedSize < targetLine) {
1510                mRemainingSpans.set(span.mIndex, false);
1511            }
1512        } else {
1513            final int line = span.getEndLine();
1514            if (line - deletedSize > targetLine) {
1515                mRemainingSpans.set(span.mIndex, false);
1516            }
1517        }
1518    }
1519
1520    private int getMaxStart(int def) {
1521        int maxStart = mSpans[0].getStartLine(def);
1522        for (int i = 1; i < mSpanCount; i++) {
1523            final int spanStart = mSpans[i].getStartLine(def);
1524            if (spanStart > maxStart) {
1525                maxStart = spanStart;
1526            }
1527        }
1528        return maxStart;
1529    }
1530
1531    private int getMinStart(int def) {
1532        int minStart = mSpans[0].getStartLine(def);
1533        for (int i = 1; i < mSpanCount; i++) {
1534            final int spanStart = mSpans[i].getStartLine(def);
1535            if (spanStart < minStart) {
1536                minStart = spanStart;
1537            }
1538        }
1539        return minStart;
1540    }
1541
1542    private int getMaxEnd(int def) {
1543        int maxEnd = mSpans[0].getEndLine(def);
1544        for (int i = 1; i < mSpanCount; i++) {
1545            final int spanEnd = mSpans[i].getEndLine(def);
1546            if (spanEnd > maxEnd) {
1547                maxEnd = spanEnd;
1548            }
1549        }
1550        return maxEnd;
1551    }
1552
1553    private int getMinEnd(int def) {
1554        int minEnd = mSpans[0].getEndLine(def);
1555        for (int i = 1; i < mSpanCount; i++) {
1556            final int spanEnd = mSpans[i].getEndLine(def);
1557            if (spanEnd < minEnd) {
1558                minEnd = spanEnd;
1559            }
1560        }
1561        return minEnd;
1562    }
1563
1564    private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
1565        if (DEBUG) {
1566            Log.d(TAG, "recycling from start for line " + line);
1567        }
1568        while (getChildCount() > 0) {
1569            View child = getChildAt(0);
1570            if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
1571                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1572                if (lp.mFullSpan) {
1573                    for (int j = 0; j < mSpanCount; j++) {
1574                        mSpans[j].popStart();
1575                    }
1576                } else {
1577                    lp.mSpan.popStart();
1578                }
1579                removeAndRecycleView(child, recycler);
1580            } else {
1581                return;// done
1582            }
1583        }
1584    }
1585
1586    private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
1587        final int childCount = getChildCount();
1588        int i;
1589        for (i = childCount - 1; i >= 0; i--) {
1590            View child = getChildAt(i);
1591            if (mPrimaryOrientation.getDecoratedStart(child) > line) {
1592                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1593                if (lp.mFullSpan) {
1594                    for (int j = 0; j < mSpanCount; j++) {
1595                        mSpans[j].popEnd();
1596                    }
1597                } else {
1598                    lp.mSpan.popEnd();
1599                }
1600                removeAndRecycleView(child, recycler);
1601            } else {
1602                return;// done
1603            }
1604        }
1605    }
1606
1607    /**
1608     * @return True if last span is the first one we want to fill
1609     */
1610    private boolean preferLastSpan(int layoutDir) {
1611        if (mOrientation == HORIZONTAL) {
1612            return (layoutDir == LAYOUT_START) != mShouldReverseLayout;
1613        }
1614        return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
1615    }
1616
1617    /**
1618     * Finds the span for the next view.
1619     */
1620    private Span getNextSpan(LayoutState layoutState) {
1621        final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
1622        final int startIndex, endIndex, diff;
1623        if (preferLastSpan) {
1624            startIndex = mSpanCount - 1;
1625            endIndex = -1;
1626            diff = -1;
1627        } else {
1628            startIndex = 0;
1629            endIndex = mSpanCount;
1630            diff = 1;
1631        }
1632        if (layoutState.mLayoutDirection == LAYOUT_END) {
1633            Span min = null;
1634            int minLine = Integer.MAX_VALUE;
1635            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
1636            for (int i = startIndex; i != endIndex; i += diff) {
1637                final Span other = mSpans[i];
1638                int otherLine = other.getEndLine(defaultLine);
1639                if (otherLine < minLine) {
1640                    min = other;
1641                    minLine = otherLine;
1642                }
1643            }
1644            return min;
1645        } else {
1646            Span max = null;
1647            int maxLine = Integer.MIN_VALUE;
1648            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
1649            for (int i = startIndex; i != endIndex; i += diff) {
1650                final Span other = mSpans[i];
1651                int otherLine = other.getStartLine(defaultLine);
1652                if (otherLine > maxLine) {
1653                    max = other;
1654                    maxLine = otherLine;
1655                }
1656            }
1657            return max;
1658        }
1659    }
1660
1661    @Override
1662    public boolean canScrollVertically() {
1663        return mOrientation == VERTICAL;
1664    }
1665
1666    @Override
1667    public boolean canScrollHorizontally() {
1668        return mOrientation == HORIZONTAL;
1669    }
1670
1671    @Override
1672    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
1673            RecyclerView.State state) {
1674        return scrollBy(dx, recycler, state);
1675    }
1676
1677    @Override
1678    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
1679            RecyclerView.State state) {
1680        return scrollBy(dy, recycler, state);
1681    }
1682
1683    private int calculateScrollDirectionForPosition(int position) {
1684        if (getChildCount() == 0) {
1685            return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
1686        }
1687        final int firstChildPos = getFirstChildPosition();
1688        return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
1689    }
1690
1691    @Override
1692    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
1693            int position) {
1694        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
1695            @Override
1696            public PointF computeScrollVectorForPosition(int targetPosition) {
1697                final int direction = calculateScrollDirectionForPosition(targetPosition);
1698                if (direction == 0) {
1699                    return null;
1700                }
1701                if (mOrientation == HORIZONTAL) {
1702                    return new PointF(direction, 0);
1703                } else {
1704                    return new PointF(0, direction);
1705                }
1706            }
1707        };
1708        scroller.setTargetPosition(position);
1709        startSmoothScroll(scroller);
1710    }
1711
1712    @Override
1713    public void scrollToPosition(int position) {
1714        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
1715            mPendingSavedState.invalidateAnchorPositionInfo();
1716        }
1717        mPendingScrollPosition = position;
1718        mPendingScrollPositionOffset = INVALID_OFFSET;
1719        requestLayout();
1720    }
1721
1722    /**
1723     * Scroll to the specified adapter position with the given offset from layout start.
1724     * <p>
1725     * Note that scroll position change will not be reflected until the next layout call.
1726     * <p>
1727     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
1728     *
1729     * @param position Index (starting at 0) of the reference item.
1730     * @param offset   The distance (in pixels) between the start edge of the item view and
1731     *                 start edge of the RecyclerView.
1732     * @see #setReverseLayout(boolean)
1733     * @see #scrollToPosition(int)
1734     */
1735    public void scrollToPositionWithOffset(int position, int offset) {
1736        if (mPendingSavedState != null) {
1737            mPendingSavedState.invalidateAnchorPositionInfo();
1738        }
1739        mPendingScrollPosition = position;
1740        mPendingScrollPositionOffset = offset;
1741        requestLayout();
1742    }
1743
1744    int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
1745        ensureOrientationHelper();
1746        final int referenceChildPosition;
1747        if (dt > 0) { // layout towards end
1748            mLayoutState.mLayoutDirection = LAYOUT_END;
1749            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
1750                    : ITEM_DIRECTION_TAIL;
1751            referenceChildPosition = getLastChildPosition();
1752        } else {
1753            mLayoutState.mLayoutDirection = LAYOUT_START;
1754            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
1755                    : ITEM_DIRECTION_HEAD;
1756            referenceChildPosition = getFirstChildPosition();
1757        }
1758        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
1759        final int absDt = Math.abs(dt);
1760        mLayoutState.mAvailable = absDt;
1761        mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
1762        int consumed = fill(recycler, mLayoutState, state);
1763        final int totalScroll;
1764        if (absDt < consumed) {
1765            totalScroll = dt;
1766        } else if (dt < 0) {
1767            totalScroll = -consumed;
1768        } else { // dt > 0
1769            totalScroll = consumed;
1770        }
1771        if (DEBUG) {
1772            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
1773        }
1774
1775        mPrimaryOrientation.offsetChildren(-totalScroll);
1776        // always reset this if we scroll for a proper save instance state
1777        mLastLayoutFromEnd = mShouldReverseLayout;
1778        return totalScroll;
1779    }
1780
1781    private int getLastChildPosition() {
1782        final int childCount = getChildCount();
1783        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
1784    }
1785
1786    private int getFirstChildPosition() {
1787        final int childCount = getChildCount();
1788        return childCount == 0 ? 0 : getPosition(getChildAt(0));
1789    }
1790
1791    /**
1792     * Finds the first View that can be used as an anchor View.
1793     *
1794     * @return Position of the View or 0 if it cannot find any such View.
1795     */
1796    private int findFirstReferenceChildPosition(int itemCount) {
1797        final int limit = getChildCount();
1798        for (int i = 0; i < limit; i++) {
1799            final View view = getChildAt(i);
1800            final int position = getPosition(view);
1801            if (position >= 0 && position < itemCount) {
1802                return position;
1803            }
1804        }
1805        return 0;
1806    }
1807
1808    /**
1809     * Finds the last View that can be used as an anchor View.
1810     *
1811     * @return Position of the View or 0 if it cannot find any such View.
1812     */
1813    private int findLastReferenceChildPosition(int itemCount) {
1814        for (int i = getChildCount() - 1; i >= 0; i--) {
1815            final View view = getChildAt(i);
1816            final int position = getPosition(view);
1817            if (position >= 0 && position < itemCount) {
1818                return position;
1819            }
1820        }
1821        return 0;
1822    }
1823
1824    @Override
1825    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1826        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1827                ViewGroup.LayoutParams.WRAP_CONTENT);
1828    }
1829
1830    @Override
1831    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
1832        return new LayoutParams(c, attrs);
1833    }
1834
1835    @Override
1836    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1837        if (lp instanceof ViewGroup.MarginLayoutParams) {
1838            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
1839        } else {
1840            return new LayoutParams(lp);
1841        }
1842    }
1843
1844    @Override
1845    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
1846        return lp instanceof LayoutParams;
1847    }
1848
1849    public int getOrientation() {
1850        return mOrientation;
1851    }
1852
1853
1854    /**
1855     * LayoutParams used by StaggeredGridLayoutManager.
1856     */
1857    public static class LayoutParams extends RecyclerView.LayoutParams {
1858
1859        /**
1860         * Span Id for Views that are not laid out yet.
1861         */
1862        public static final int INVALID_SPAN_ID = -1;
1863
1864        // Package scope to be able to access from tests.
1865        Span mSpan;
1866
1867        boolean mFullSpan;
1868
1869        public LayoutParams(Context c, AttributeSet attrs) {
1870            super(c, attrs);
1871        }
1872
1873        public LayoutParams(int width, int height) {
1874            super(width, height);
1875        }
1876
1877        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1878            super(source);
1879        }
1880
1881        public LayoutParams(ViewGroup.LayoutParams source) {
1882            super(source);
1883        }
1884
1885        public LayoutParams(RecyclerView.LayoutParams source) {
1886            super(source);
1887        }
1888
1889        /**
1890         * When set to true, the item will layout using all span area. That means, if orientation
1891         * is vertical, the view will have full width; if orientation is horizontal, the view will
1892         * have full height.
1893         *
1894         * @param fullSpan True if this item should traverse all spans.
1895         */
1896        public void setFullSpan(boolean fullSpan) {
1897            mFullSpan = fullSpan;
1898        }
1899
1900        /**
1901         * Returns the Span index to which this View is assigned.
1902         *
1903         * @return The Span index of the View. If View is not yet assigned to any span, returns
1904         * {@link #INVALID_SPAN_ID}.
1905         */
1906        public final int getSpanIndex() {
1907            if (mSpan == null) {
1908                return INVALID_SPAN_ID;
1909            }
1910            return mSpan.mIndex;
1911        }
1912    }
1913
1914    // Package scoped to access from tests.
1915    class Span {
1916
1917        static final int INVALID_LINE = Integer.MIN_VALUE;
1918        private ArrayList<View> mViews = new ArrayList<View>();
1919        int mCachedStart = INVALID_LINE;
1920        int mCachedEnd = INVALID_LINE;
1921        int mDeletedSize = 0;
1922        final int mIndex;
1923
1924        private Span(int index) {
1925            mIndex = index;
1926        }
1927
1928        int getStartLine(int def) {
1929            if (mCachedStart != INVALID_LINE) {
1930                return mCachedStart;
1931            }
1932            if (mViews.size() == 0) {
1933                return def;
1934            }
1935            calculateCachedStart();
1936            return mCachedStart;
1937        }
1938
1939        void calculateCachedStart() {
1940            final View startView = mViews.get(0);
1941            final LayoutParams lp = getLayoutParams(startView);
1942            mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
1943            if (lp.mFullSpan) {
1944                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
1945                        .getFullSpanItem(lp.getViewPosition());
1946                if (fsi != null && fsi.mGapDir == LAYOUT_START) {
1947                    mCachedStart -= fsi.getGapForSpan(mIndex);
1948                }
1949            }
1950        }
1951
1952        // Use this one when default value does not make sense and not having a value means a bug.
1953        int getStartLine() {
1954            if (mCachedStart != INVALID_LINE) {
1955                return mCachedStart;
1956            }
1957            calculateCachedStart();
1958            return mCachedStart;
1959        }
1960
1961        int getEndLine(int def) {
1962            if (mCachedEnd != INVALID_LINE) {
1963                return mCachedEnd;
1964            }
1965            final int size = mViews.size();
1966            if (size == 0) {
1967                return def;
1968            }
1969            calculateCachedEnd();
1970            return mCachedEnd;
1971        }
1972
1973        void calculateCachedEnd() {
1974            final View endView = mViews.get(mViews.size() - 1);
1975            final LayoutParams lp = getLayoutParams(endView);
1976            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
1977            if (lp.mFullSpan) {
1978                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
1979                        .getFullSpanItem(lp.getViewPosition());
1980                if (fsi != null && fsi.mGapDir == LAYOUT_END) {
1981                    mCachedEnd += fsi.getGapForSpan(mIndex);
1982                }
1983            }
1984        }
1985
1986        // Use this one when default value does not make sense and not having a value means a bug.
1987        int getEndLine() {
1988            if (mCachedEnd != INVALID_LINE) {
1989                return mCachedEnd;
1990            }
1991            calculateCachedEnd();
1992            return mCachedEnd;
1993        }
1994
1995        void prependToSpan(View view) {
1996            LayoutParams lp = getLayoutParams(view);
1997            lp.mSpan = this;
1998            mViews.add(0, view);
1999            mCachedStart = INVALID_LINE;
2000            if (mViews.size() == 1) {
2001                mCachedEnd = INVALID_LINE;
2002            }
2003            if (lp.isItemRemoved() || lp.isItemChanged()) {
2004                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2005            }
2006        }
2007
2008        void appendToSpan(View view) {
2009            LayoutParams lp = getLayoutParams(view);
2010            lp.mSpan = this;
2011            mViews.add(view);
2012            mCachedEnd = INVALID_LINE;
2013            if (mViews.size() == 1) {
2014                mCachedStart = INVALID_LINE;
2015            }
2016            if (lp.isItemRemoved() || lp.isItemChanged()) {
2017                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2018            }
2019        }
2020
2021        // Useful method to preserve positions on a re-layout.
2022        void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
2023            int reference;
2024            if (reverseLayout) {
2025                reference = getEndLine(INVALID_LINE);
2026            } else {
2027                reference = getStartLine(INVALID_LINE);
2028            }
2029            clear();
2030            if (reference == INVALID_LINE) {
2031                return;
2032            }
2033            if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
2034                    (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding()) ) {
2035                return;
2036            }
2037            if (offset != INVALID_OFFSET) {
2038                reference += offset;
2039            }
2040            mCachedStart = mCachedEnd = reference;
2041        }
2042
2043        void clear() {
2044            mViews.clear();
2045            invalidateCache();
2046            mDeletedSize = 0;
2047        }
2048
2049        void invalidateCache() {
2050            mCachedStart = INVALID_LINE;
2051            mCachedEnd = INVALID_LINE;
2052        }
2053
2054        void setLine(int line) {
2055            mCachedEnd = mCachedStart = line;
2056        }
2057
2058        void popEnd() {
2059            final int size = mViews.size();
2060            View end = mViews.remove(size - 1);
2061            final LayoutParams lp = getLayoutParams(end);
2062            lp.mSpan = null;
2063            if (lp.isItemRemoved() || lp.isItemChanged()) {
2064                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
2065            }
2066            if (size == 1) {
2067                mCachedStart = INVALID_LINE;
2068            }
2069            mCachedEnd = INVALID_LINE;
2070        }
2071
2072        void popStart() {
2073            View start = mViews.remove(0);
2074            final LayoutParams lp = getLayoutParams(start);
2075            lp.mSpan = null;
2076            if (mViews.size() == 0) {
2077                mCachedEnd = INVALID_LINE;
2078            }
2079            if (lp.isItemRemoved() || lp.isItemChanged()) {
2080                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
2081            }
2082            mCachedStart = INVALID_LINE;
2083        }
2084
2085        public int getDeletedSize() {
2086            return mDeletedSize;
2087        }
2088
2089        LayoutParams getLayoutParams(View view) {
2090            return (LayoutParams) view.getLayoutParams();
2091        }
2092
2093        void onOffset(int dt) {
2094            if (mCachedStart != INVALID_LINE) {
2095                mCachedStart += dt;
2096            }
2097            if (mCachedEnd != INVALID_LINE) {
2098                mCachedEnd += dt;
2099            }
2100        }
2101
2102        // normalized offset is how much this span can scroll
2103        int getNormalizedOffset(int dt, int targetStart, int targetEnd) {
2104            if (mViews.size() == 0) {
2105                return 0;
2106            }
2107            if (dt < 0) {
2108                final int endSpace = getEndLine() - targetEnd;
2109                if (endSpace <= 0) {
2110                    return 0;
2111                }
2112                return -dt > endSpace ? -endSpace : dt;
2113            } else {
2114                final int startSpace = targetStart - getStartLine();
2115                if (startSpace <= 0) {
2116                    return 0;
2117                }
2118                return startSpace < dt ? startSpace : dt;
2119            }
2120        }
2121
2122        /**
2123         * Returns if there is no child between start-end lines
2124         *
2125         * @param start The start line
2126         * @param end   The end line
2127         * @return true if a new child can be added between start and end
2128         */
2129        boolean isEmpty(int start, int end) {
2130            final int count = mViews.size();
2131            for (int i = 0; i < count; i++) {
2132                final View view = mViews.get(i);
2133                if (mPrimaryOrientation.getDecoratedStart(view) < end &&
2134                        mPrimaryOrientation.getDecoratedEnd(view) > start) {
2135                    return false;
2136                }
2137            }
2138            return true;
2139        }
2140
2141        public int findFirstVisibleItemPosition() {
2142            return mReverseLayout
2143                    ? findOneVisibleChild(mViews.size() -1, -1, false)
2144                    : findOneVisibleChild(0, mViews.size(), false);
2145        }
2146
2147        public int findFirstCompletelyVisibleItemPosition() {
2148            return mReverseLayout
2149                    ? findOneVisibleChild(mViews.size() -1, -1, true)
2150                    : findOneVisibleChild(0, mViews.size(), true);
2151        }
2152
2153        public int findLastVisibleItemPosition() {
2154            return mReverseLayout
2155                    ? findOneVisibleChild(0, mViews.size(), false)
2156                    : findOneVisibleChild(mViews.size() -1, -1, false);
2157        }
2158
2159        public int findLastCompletelyVisibleItemPosition() {
2160            return mReverseLayout
2161                    ? findOneVisibleChild(0, mViews.size(), true)
2162                    : findOneVisibleChild(mViews.size() -1, -1, true);
2163        }
2164
2165        int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
2166            final int start = mPrimaryOrientation.getStartAfterPadding();
2167            final int end = mPrimaryOrientation.getEndAfterPadding();
2168            final int next = toIndex > fromIndex ? 1 : -1;
2169            for (int i = fromIndex; i != toIndex; i += next) {
2170                final View child = mViews.get(i);
2171                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
2172                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
2173                if (childStart < end && childEnd > start) {
2174                    if (completelyVisible) {
2175                        if (childStart >= start && childEnd <= end) {
2176                            return getPosition(child);
2177                        }
2178                    } else {
2179                        return getPosition(child);
2180                    }
2181                }
2182            }
2183            return NO_POSITION;
2184        }
2185    }
2186
2187    /**
2188     * An array of mappings from adapter position to span.
2189     * This only grows when a write happens and it grows up to the size of the adapter.
2190     */
2191    static class LazySpanLookup {
2192
2193        private static final int MIN_SIZE = 10;
2194        int[] mData;
2195        int mAdapterSize; // we don't want to grow beyond that, unless it grows
2196        List<FullSpanItem> mFullSpanItems;
2197
2198
2199        /**
2200         * Invalidates everything after this position, including full span information
2201         */
2202        int forceInvalidateAfter(int position) {
2203            if (mFullSpanItems != null) {
2204                for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2205                    FullSpanItem fsi = mFullSpanItems.get(i);
2206                    if (fsi.mPosition >= position) {
2207                        mFullSpanItems.remove(i);
2208                    }
2209                }
2210            }
2211            return invalidateAfter(position);
2212        }
2213
2214        /**
2215         * returns end position for invalidation.
2216         */
2217        int invalidateAfter(int position) {
2218            if (mData == null) {
2219                return RecyclerView.NO_POSITION;
2220            }
2221            if (position >= mData.length) {
2222                return RecyclerView.NO_POSITION;
2223            }
2224            int endPosition = invalidateFullSpansAfter(position);
2225            if (endPosition == RecyclerView.NO_POSITION) {
2226                Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
2227                return mData.length;
2228            } else {
2229                // just invalidate items in between
2230                Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID);
2231                return endPosition + 1;
2232            }
2233        }
2234
2235        int getSpan(int position) {
2236            if (mData == null || position >= mData.length) {
2237                return LayoutParams.INVALID_SPAN_ID;
2238            } else {
2239                return mData[position];
2240            }
2241        }
2242
2243        void setSpan(int position, Span span) {
2244            ensureSize(position);
2245            mData[position] = span.mIndex;
2246        }
2247
2248        int sizeForPosition(int position) {
2249            int len = mData.length;
2250            while (len <= position) {
2251                len *= 2;
2252            }
2253            if (len > mAdapterSize) {
2254                len = mAdapterSize;
2255            }
2256            return len;
2257        }
2258
2259        void ensureSize(int position) {
2260            if (mData == null) {
2261                mData = new int[Math.max(position, MIN_SIZE) + 1];
2262                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2263            } else if (position >= mData.length) {
2264                int[] old = mData;
2265                mData = new int[sizeForPosition(position)];
2266                System.arraycopy(old, 0, mData, 0, old.length);
2267                Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
2268            }
2269        }
2270
2271        void clear() {
2272            if (mData != null) {
2273                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2274            }
2275            mFullSpanItems = null;
2276        }
2277
2278        void offsetForRemoval(int positionStart, int itemCount) {
2279            if (mData == null || positionStart >= mData.length) {
2280                return;
2281            }
2282            ensureSize(positionStart + itemCount);
2283            System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
2284                    mData.length - positionStart - itemCount);
2285            Arrays.fill(mData, mData.length - itemCount, mData.length,
2286                    LayoutParams.INVALID_SPAN_ID);
2287            offsetFullSpansForRemoval(positionStart, itemCount);
2288        }
2289
2290        private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
2291            if (mFullSpanItems == null) {
2292                return;
2293            }
2294            final int end = positionStart + itemCount;
2295            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2296                FullSpanItem fsi = mFullSpanItems.get(i);
2297                if (fsi.mPosition < positionStart) {
2298                    continue;
2299                }
2300                if (fsi.mPosition < end) {
2301                    mFullSpanItems.remove(i);
2302                } else {
2303                    fsi.mPosition -= itemCount;
2304                }
2305            }
2306        }
2307
2308        void offsetForAddition(int positionStart, int itemCount) {
2309            if (mData == null || positionStart >= mData.length) {
2310                return;
2311            }
2312            ensureSize(positionStart + itemCount);
2313            System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
2314                    mData.length - positionStart - itemCount);
2315            Arrays.fill(mData, positionStart, positionStart + itemCount,
2316                    LayoutParams.INVALID_SPAN_ID);
2317            offsetFullSpansForAddition(positionStart, itemCount);
2318        }
2319
2320        private void offsetFullSpansForAddition(int positionStart, int itemCount) {
2321            if (mFullSpanItems == null) {
2322                return;
2323            }
2324            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2325                FullSpanItem fsi = mFullSpanItems.get(i);
2326                if (fsi.mPosition < positionStart) {
2327                    continue;
2328                }
2329                fsi.mPosition += itemCount;
2330            }
2331        }
2332
2333        /**
2334         * Returns when invalidation should end. e.g. hitting a full span position.
2335         * Returned position SHOULD BE invalidated.
2336         */
2337        private int invalidateFullSpansAfter(int position) {
2338            if (mFullSpanItems == null) {
2339                return RecyclerView.NO_POSITION;
2340            }
2341            final FullSpanItem item = getFullSpanItem(position);
2342            // if there is an fsi at this position, get rid of it.
2343            if (item != null) {
2344                mFullSpanItems.remove(item);
2345            }
2346            int nextFsiIndex = -1;
2347            final int count = mFullSpanItems.size();
2348            for (int i = 0; i < count; i++) {
2349                FullSpanItem fsi = mFullSpanItems.get(i);
2350                if (fsi.mPosition >= position) {
2351                    nextFsiIndex = i;
2352                    break;
2353                }
2354            }
2355            if (nextFsiIndex != -1) {
2356                FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
2357                mFullSpanItems.remove(nextFsiIndex);
2358                return fsi.mPosition;
2359            }
2360            return RecyclerView.NO_POSITION;
2361        }
2362
2363        public void addFullSpanItem(FullSpanItem fullSpanItem) {
2364            if (mFullSpanItems == null) {
2365                mFullSpanItems = new ArrayList<FullSpanItem>();
2366            }
2367            final int size = mFullSpanItems.size();
2368            for (int i = 0; i < size; i++) {
2369                FullSpanItem other = mFullSpanItems.get(i);
2370                if (other.mPosition == fullSpanItem.mPosition) {
2371                    if (DEBUG) {
2372                        throw new IllegalStateException("two fsis for same position");
2373                    } else {
2374                        mFullSpanItems.remove(i);
2375                    }
2376                }
2377                if (other.mPosition >= fullSpanItem.mPosition) {
2378                    mFullSpanItems.add(i, fullSpanItem);
2379                    return;
2380                }
2381            }
2382            // if it is not added to a position.
2383            mFullSpanItems.add(fullSpanItem);
2384        }
2385
2386        public FullSpanItem getFullSpanItem(int position) {
2387            if (mFullSpanItems == null) {
2388                return null;
2389            }
2390            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2391                final FullSpanItem fsi = mFullSpanItems.get(i);
2392                if (fsi.mPosition == position) {
2393                    return fsi;
2394                }
2395            }
2396            return null;
2397        }
2398
2399        /**
2400         * @param minPos inclusive
2401         * @param maxPos exclusive
2402         * @param gapDir if not 0, returns FSIs on in that direction
2403         */
2404        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir) {
2405            if (mFullSpanItems == null) {
2406                return null;
2407            }
2408            for (int i = 0; i < mFullSpanItems.size(); i++) {
2409                FullSpanItem fsi = mFullSpanItems.get(i);
2410                if (fsi.mPosition >= maxPos) {
2411                    return null;
2412                }
2413                if (fsi.mPosition >= minPos && (gapDir == 0 || fsi.mGapDir == gapDir)) {
2414                    return fsi;
2415                }
2416            }
2417            return null;
2418        }
2419
2420        /**
2421         * We keep information about full span items because they may create gaps in the UI.
2422         */
2423        static class FullSpanItem implements Parcelable {
2424
2425            int mPosition;
2426            int mGapDir;
2427            int[] mGapPerSpan;
2428
2429            public FullSpanItem(Parcel in) {
2430                mPosition = in.readInt();
2431                mGapDir = in.readInt();
2432                int spanCount = in.readInt();
2433                if (spanCount > 0) {
2434                    mGapPerSpan = new int[spanCount];
2435                    in.readIntArray(mGapPerSpan);
2436                }
2437            }
2438
2439            public FullSpanItem() {
2440            }
2441
2442            int getGapForSpan(int spanIndex) {
2443                return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
2444            }
2445
2446            public void invalidateSpanGaps() {
2447                mGapPerSpan = null;
2448            }
2449
2450            @Override
2451            public int describeContents() {
2452                return 0;
2453            }
2454
2455            @Override
2456            public void writeToParcel(Parcel dest, int flags) {
2457                dest.writeInt(mPosition);
2458                dest.writeInt(mGapDir);
2459                if (mGapPerSpan != null && mGapPerSpan.length > 0) {
2460                    dest.writeInt(mGapPerSpan.length);
2461                    dest.writeIntArray(mGapPerSpan);
2462                } else {
2463                    dest.writeInt(0);
2464                }
2465            }
2466
2467            @Override
2468            public String toString() {
2469                return "FullSpanItem{" +
2470                        "mPosition=" + mPosition +
2471                        ", mGapDir=" + mGapDir +
2472                        ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
2473                        '}';
2474            }
2475
2476            public static final Parcelable.Creator<FullSpanItem> CREATOR
2477                    = new Parcelable.Creator<FullSpanItem>() {
2478                @Override
2479                public FullSpanItem createFromParcel(Parcel in) {
2480                    return new FullSpanItem(in);
2481                }
2482
2483                @Override
2484                public FullSpanItem[] newArray(int size) {
2485                    return new FullSpanItem[size];
2486                }
2487            };
2488        }
2489    }
2490
2491    static class SavedState implements Parcelable {
2492
2493        int mAnchorPosition;
2494        int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
2495        int mSpanOffsetsSize;
2496        int[] mSpanOffsets;
2497        int mSpanLookupSize;
2498        int[] mSpanLookup;
2499        List<LazySpanLookup.FullSpanItem> mFullSpanItems;
2500        boolean mReverseLayout;
2501        boolean mAnchorLayoutFromEnd;
2502        boolean mLastLayoutRTL;
2503
2504        public SavedState() {
2505        }
2506
2507        SavedState(Parcel in) {
2508            mAnchorPosition = in.readInt();
2509            mVisibleAnchorPosition = in.readInt();
2510            mSpanOffsetsSize = in.readInt();
2511            if (mSpanOffsetsSize > 0) {
2512                mSpanOffsets = new int[mSpanOffsetsSize];
2513                in.readIntArray(mSpanOffsets);
2514            }
2515
2516            mSpanLookupSize = in.readInt();
2517            if (mSpanLookupSize > 0) {
2518                mSpanLookup = new int[mSpanLookupSize];
2519                in.readIntArray(mSpanLookup);
2520            }
2521            mReverseLayout = in.readInt() == 1;
2522            mAnchorLayoutFromEnd = in.readInt() == 1;
2523            mLastLayoutRTL = in.readInt() == 1;
2524            mFullSpanItems = in.readArrayList(
2525                    LazySpanLookup.FullSpanItem.class.getClassLoader());
2526        }
2527
2528        public SavedState(SavedState other) {
2529            mSpanOffsetsSize = other.mSpanOffsetsSize;
2530            mAnchorPosition = other.mAnchorPosition;
2531            mVisibleAnchorPosition = other.mVisibleAnchorPosition;
2532            mSpanOffsets = other.mSpanOffsets;
2533            mSpanLookupSize = other.mSpanLookupSize;
2534            mSpanLookup = other.mSpanLookup;
2535            mReverseLayout = other.mReverseLayout;
2536            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
2537            mLastLayoutRTL = other.mLastLayoutRTL;
2538            mFullSpanItems = other.mFullSpanItems;
2539        }
2540
2541        void invalidateSpanInfo() {
2542            mSpanOffsets = null;
2543            mSpanOffsetsSize = 0;
2544            mSpanLookupSize = 0;
2545            mSpanLookup = null;
2546            mFullSpanItems = null;
2547        }
2548
2549        void invalidateAnchorPositionInfo() {
2550            mSpanOffsets = null;
2551            mSpanOffsetsSize = 0;
2552            mAnchorPosition = NO_POSITION;
2553            mVisibleAnchorPosition = NO_POSITION;
2554        }
2555
2556        @Override
2557        public int describeContents() {
2558            return 0;
2559        }
2560
2561        @Override
2562        public void writeToParcel(Parcel dest, int flags) {
2563            dest.writeInt(mAnchorPosition);
2564            dest.writeInt(mVisibleAnchorPosition);
2565            dest.writeInt(mSpanOffsetsSize);
2566            if (mSpanOffsetsSize > 0) {
2567                dest.writeIntArray(mSpanOffsets);
2568            }
2569            dest.writeInt(mSpanLookupSize);
2570            if (mSpanLookupSize > 0) {
2571                dest.writeIntArray(mSpanLookup);
2572            }
2573            dest.writeInt(mReverseLayout ? 1 : 0);
2574            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
2575            dest.writeInt(mLastLayoutRTL ? 1 : 0);
2576            dest.writeList(mFullSpanItems);
2577        }
2578
2579        public static final Parcelable.Creator<SavedState> CREATOR
2580                = new Parcelable.Creator<SavedState>() {
2581            @Override
2582            public SavedState createFromParcel(Parcel in) {
2583                return new SavedState(in);
2584            }
2585
2586            @Override
2587            public SavedState[] newArray(int size) {
2588                return new SavedState[size];
2589            }
2590        };
2591    }
2592
2593    /**
2594     * Data class to hold the information about an anchor position which is used in onLayout call.
2595     */
2596    private class AnchorInfo {
2597
2598        int mPosition;
2599        int mOffset;
2600        boolean mLayoutFromEnd;
2601        boolean mInvalidateOffsets;
2602
2603        void reset() {
2604            mPosition = NO_POSITION;
2605            mOffset = INVALID_OFFSET;
2606            mLayoutFromEnd = false;
2607            mInvalidateOffsets = false;
2608        }
2609
2610        void assignCoordinateFromPadding() {
2611            mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
2612                    : mPrimaryOrientation.getStartAfterPadding();
2613        }
2614
2615        void assignCoordinateFromPadding(int addedDistance) {
2616            if (mLayoutFromEnd) {
2617                mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
2618            } else {
2619                mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;
2620            }
2621        }
2622    }
2623}
2624