StaggeredGridLayoutManager.java revision 3ed05355fded55e438477b23a1864c3b6d129342
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.util.AttributeSet;
26import android.util.Log;
27import android.view.View;
28import android.view.ViewGroup;
29
30
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.BitSet;
34import java.util.List;
35
36
37import static android.support.v7.widget.LayoutState.LAYOUT_START;
38import static android.support.v7.widget.LayoutState.LAYOUT_END;
39import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
40import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
41import static android.support.v7.widget.OrientationHelper.HORIZONTAL;
42import static android.support.v7.widget.OrientationHelper.VERTICAL;
43
44/**
45 * A LayoutManager that lays out children in a staggered grid formation.
46 * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
47 * <p>
48 * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
49 * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
50 * control this behavior via {@link #setGapStrategy(int)}.
51 */
52public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
53
54    public static final String TAG = "StaggeredGridLayoutManager";
55
56    private static final boolean DEBUG = false;
57
58    /**
59     * Does not do anything to hide gaps
60     */
61    public static final int GAP_HANDLING_NONE = 0;
62
63    /**
64     * Scroll the shorter span slower to avoid gaps in the UI.
65     * <p>
66     * For example, if LayoutManager ends up with the following layout:
67     * <code>
68     * BXC
69     * DEF
70     * </code>
71     * Where B has two spans height, if user scrolls down it will keep the positions of 2nd and 3rd
72     * columns,
73     * which will result in:
74     * <code>
75     * BXC
76     * BEF
77     * </code>
78     * instead of
79     * <code>
80     * B
81     * BEF
82     * </code>
83     */
84    public static final int GAP_HANDLING_LAZY = 1;
85
86    /**
87     * On scroll, LayoutManager checks for a view that is assigned to wrong span.
88     * When such a situation is detected, LayoutManager will wait until scroll is complete and then
89     * move children to their correct spans.
90     * <p>
91     * For example, if LayoutManager ends up with the following layout due to adapter changes:
92     * <code>
93     * AAA
94     * _BC
95     * DDD
96     * </code>
97     * It will animate to the following state:
98     * <code>
99     * AAA
100     * BC_
101     * DDD
102     * </code>
103     */
104    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
105
106    private static final int INVALID_OFFSET = Integer.MIN_VALUE;
107
108    /**
109     * Number of spans
110     */
111    private int mSpanCount = -1;
112
113    private Span[] mSpans;
114
115    /**
116     * Primary orientation is the layout's orientation, secondary orientation is the orientation
117     * for spans. Having both makes code much cleaner for calculations.
118     */
119    OrientationHelper mPrimaryOrientation;
120    OrientationHelper mSecondaryOrientation;
121
122    private int mOrientation;
123
124    /**
125     * The width or height per span, depending on the orientation.
126     */
127    private int mSizePerSpan;
128
129    private LayoutState mLayoutState;
130
131    private boolean mReverseLayout = false;
132
133    /**
134     * Aggregated reverse layout value that takes RTL into account.
135     */
136    private boolean mShouldReverseLayout = false;
137
138    /**
139     * Temporary variable used during fill method to check which spans needs to be filled.
140     */
141    private BitSet mRemainingSpans;
142
143    /**
144     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
145     * layout which will check this variable and re-layout accordingly.
146     */
147    private int mPendingScrollPosition = RecyclerView.NO_POSITION;
148
149    /**
150     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
151     * called.
152     */
153    private int mPendingScrollPositionOffset = INVALID_OFFSET;
154
155    /**
156     * Keeps the mapping between the adapter positions and spans. This is necessary to provide
157     * a consistent experience when user scrolls the list.
158     */
159    LazySpanLookup mLazySpanLookup = new LazySpanLookup();
160
161    /**
162     * how we handle gaps in UI.
163     */
164    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
165
166    /**
167     * This list is used to keep track of views that we should remove. Due to complexity of not
168     * adding views, StaggeredGridLayoutManager keeps a list of children to be removed and removes
169     * them after first layout pass.
170     * TODO this will not be necessary when RV is updated not to require laying out not-added
171     * children
172     */
173    private final List<View> mChildrenToBeRemoved = new ArrayList<View>();
174
175    /**
176     * Saved state needs this information to properly layout on restore.
177     */
178    private boolean mLastLayoutFromEnd;
179
180    /**
181     * SavedState is not handled until a layout happens. This is where we keep it until next
182     * layout.
183     */
184    private SavedState mPendingSavedState;
185
186    /**
187     * If LayoutManager detects an unwanted gap in the layout, it sets this flag which will trigger
188     * a runnable after scrolling ends and will re-check. If invalid view state is still present,
189     * it will request a layout to fix it.
190     */
191    private boolean mHasGaps;
192
193    /**
194     * Creates a StaggeredGridLayoutManager with given parameters.
195     *
196     * @param spanCount   If orientation is vertical, spanCount is number of columns. If
197     *                    orientation is horizontal, spanCount is number of rows.
198     * @param orientation {@link OrientationHelper#VERTICAL} or {@link OrientationHelper#HORIZONTAL}
199     */
200    public StaggeredGridLayoutManager(int spanCount, int orientation) {
201        mOrientation = orientation;
202        setSpanCount(spanCount);
203    }
204
205    @Override
206    public void onScrollStateChanged(int state) {
207        if (state == RecyclerView.SCROLL_STATE_IDLE && mHasGaps) {
208            // re-check for gaps
209            View gapView = hasGapsToFix(0, getChildCount());
210            if (gapView == null) {
211                mHasGaps = false; // yay, gap disappeared :)
212                // We should invalidate positions after the last visible child. No reason to
213                // re-layout.
214                final int lastVisiblePosition = mShouldReverseLayout ? getFirstChildPosition()
215                        : getLastChildPosition();
216                mLazySpanLookup.invalidateAfter(lastVisiblePosition + 1);
217            } else {
218                mLazySpanLookup.invalidateAfter(getPosition(gapView));
219                requestLayout(); // Trigger a re-layout which will fix the layout assignments.
220            }
221        }
222    }
223
224    /**
225     * Sets the number of spans for the layout. This will invalidate all of the span assignments
226     * for Views.
227     * <p>
228     * Calling this method will automatically result in a new layout request unless the spanCount
229     * parameter is equal to current span count.
230     *
231     * @param spanCount Number of spans to layout
232     */
233    public void setSpanCount(int spanCount) {
234        if (mPendingSavedState != null && mPendingSavedState.mSpanCount != spanCount) {
235            // invalidate span info in saved state
236            mPendingSavedState.invalidateSpanInfo();
237            mPendingSavedState.mSpanCount = spanCount;
238        }
239        if (spanCount != mSpanCount) {
240            invalidateSpanAssignments();
241            mSpanCount = spanCount;
242            mRemainingSpans = new BitSet(mSpanCount);
243            mSpans = new Span[mSpanCount];
244            for (int i = 0; i < mSpanCount; i++) {
245                mSpans[i] = new Span(i);
246            }
247            requestLayout();
248        }
249    }
250
251    /**
252     * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
253     * scroll position.
254     *
255     * @param orientation {@link OrientationHelper#HORIZONTAL} or {@link OrientationHelper#VERTICAL}
256     */
257    public void setOrientation(int orientation) {
258        if (orientation != HORIZONTAL && orientation != VERTICAL) {
259            throw new IllegalArgumentException("invalid orientation.");
260        }
261        if (mPendingSavedState != null && mPendingSavedState.mOrientation != orientation) {
262            // override pending state
263            mPendingSavedState.mOrientation = orientation;
264        }
265        if (orientation == mOrientation) {
266            return;
267        }
268        mOrientation = orientation;
269        if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
270            // swap
271            OrientationHelper tmp = mPrimaryOrientation;
272            mPrimaryOrientation = mSecondaryOrientation;
273            mSecondaryOrientation = tmp;
274        }
275        requestLayout();
276    }
277
278    /**
279     * Sets whether LayoutManager should start laying out items from the end of the UI. The order
280     * items are traversed is not affected by this call.
281     * <p>
282     * This behaves similar to the layout change for RTL views. When set to true, first item is
283     * laid out at the end of the ViewGroup, second item is laid out before it etc.
284     * <p>
285     * For horizontal layouts, it depends on the layout direction.
286     * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
287     * {@link RecyclerView}} is RTL, it will layout from LTR.
288     *
289     * @param reverseLayout Whether layout should be in reverse or not
290     */
291    public void setReverseLayout(boolean reverseLayout) {
292        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
293            mPendingSavedState.mReverseLayout = reverseLayout;
294        }
295        mReverseLayout = reverseLayout;
296        requestLayout();
297    }
298
299    /**
300     * Returns the current gap handling strategy for StaggeredGridLayoutManager.
301     * <p>
302     * Staggered grid may have gaps in the layout as items may have different sizes. To avoid gaps,
303     * StaggeredGridLayoutManager provides 3 options. Check {@link #GAP_HANDLING_NONE},
304     * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}, {@link #GAP_HANDLING_LAZY} for details.
305     * <p>
306     * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
307     *
308     * @return Current gap handling strategy.
309     * @see #setGapStrategy(int)
310     * @see #GAP_HANDLING_NONE
311     * @see #GAP_HANDLING_LAZY
312     * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
313     */
314    public int getGapStrategy() {
315        return mGapStrategy;
316    }
317
318    /**
319     * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
320     * is different than the current strategy, calling this method will trigger a layout request.
321     *
322     * @param gapStrategy The new gap handling strategy. Should be {@link #GAP_HANDLING_LAZY}
323     *                    , {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or
324     *                    {@link #GAP_HANDLING_NONE}
325     * @see #getGapStrategy()
326     */
327    public void setGapStrategy(int gapStrategy) {
328        if (mPendingSavedState != null && mPendingSavedState.mGapStrategy != gapStrategy) {
329            mPendingSavedState.mGapStrategy = gapStrategy;
330        }
331        if (gapStrategy == mGapStrategy) {
332            return;
333        }
334        if (gapStrategy != GAP_HANDLING_LAZY && gapStrategy != GAP_HANDLING_NONE &&
335                gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
336            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
337                    + ", GAP_HANDLING_LAZY or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
338        }
339        mGapStrategy = gapStrategy;
340        requestLayout();
341    }
342
343    /**
344     * Returns the number of spans laid out by StaggeredGridLayoutManager.
345     *
346     * @return Number of spans in the layout
347     */
348    public int getSpanCount() {
349        return mSpanCount;
350    }
351
352    /**
353     * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
354     * <p>
355     * If you need to cancel current assignments, you can call this method which will clear all
356     * assignments and request a new layout.
357     */
358    public void invalidateSpanAssignments() {
359        mLazySpanLookup.clear();
360        requestLayout();
361    }
362
363    private void ensureOrientationHelper() {
364        if (mPrimaryOrientation == null) {
365            mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
366            mSecondaryOrientation = OrientationHelper
367                    .createOrientationHelper(this, 1 - mOrientation);
368            mLayoutState = new LayoutState();
369        }
370    }
371
372    /**
373     * Calculates the views' layout order. (e.g. from end to start or start to end)
374     * RTL layout support is applied automatically. So if layout is RTL and
375     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
376     */
377    private void resolveShouldLayoutReverse() {
378        // A == B is the same result, but we rather keep it readable
379        if (mOrientation == VERTICAL || !isLayoutRTL()) {
380            mShouldReverseLayout = mReverseLayout;
381        } else {
382            mShouldReverseLayout = !mReverseLayout;
383        }
384    }
385
386    private boolean isLayoutRTL() {
387        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
388    }
389
390    /**
391     * Returns whether views are laid out in reverse order or not.
392     * <p>
393     * Not that this value is not affected by RecyclerView's layout direction.
394     *
395     * @return True if layout is reversed, false otherwise
396     * @see #setReverseLayout(boolean)
397     */
398    public boolean getReverseLayout() {
399        return mReverseLayout;
400    }
401
402    @Override
403    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
404        ensureOrientationHelper();
405        // Update adapter size.
406        mLazySpanLookup.mAdapterSize = state.getItemCount();
407        int anchorItemPosition;
408        int anchorOffset;
409        // This value may change if we are jumping to a position.
410        boolean layoutFromEnd;
411
412        // If set to true, spans will clear their offsets and they'll be laid out from start
413        // depending on the layout direction. Invalidating span offsets is necessary to be able
414        // to jump to a position.
415        boolean invalidateSpanOffsets = false;
416
417        if (mPendingSavedState != null) {
418            if (DEBUG) {
419                Log.d(TAG, "found saved state: " + mPendingSavedState);
420            }
421            setOrientation(mPendingSavedState.mOrientation);
422            setSpanCount(mPendingSavedState.mSpanCount);
423            setGapStrategy(mPendingSavedState.mGapStrategy);
424            setReverseLayout(mPendingSavedState.mReverseLayout);
425            resolveShouldLayoutReverse();
426
427            if (mPendingSavedState.mAnchorPosition != RecyclerView.NO_POSITION) {
428                mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
429                layoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
430            } else {
431                layoutFromEnd = mShouldReverseLayout;
432            }
433            if (mPendingSavedState.mHasSpanOffsets) {
434                for (int i = 0; i < mSpanCount; i++) {
435                    mSpans[i].clear();
436                    mSpans[i].setLine(mPendingSavedState.mSpanOffsets[i]);
437                }
438            }
439            if (mPendingSavedState.mSpanLookupSize > 1) {
440                mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
441            }
442
443        } else {
444            resolveShouldLayoutReverse();
445            layoutFromEnd = mShouldReverseLayout; // get updated value.
446        }
447
448        // Validate scroll position if exists.
449        if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
450            // Validate it.
451            if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
452                mPendingScrollPosition = RecyclerView.NO_POSITION;
453                mPendingScrollPositionOffset = INVALID_OFFSET;
454            }
455        }
456
457        if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
458            if (mPendingSavedState == null
459                    || mPendingSavedState.mAnchorPosition == RecyclerView.NO_POSITION
460                    || !mPendingSavedState.mHasSpanOffsets) {
461                // If item is visible, make it fully visible.
462                final View child = findViewByPosition(mPendingScrollPosition);
463                if (child != null) {
464                    if (mPendingScrollPositionOffset != INVALID_OFFSET) {
465                        // Use regular anchor position.
466                        anchorItemPosition = mShouldReverseLayout ? getLastChildPosition()
467                                : getFirstChildPosition();
468                        if (layoutFromEnd) {
469                            final int target = mPrimaryOrientation.getEndAfterPadding() -
470                                    mPendingScrollPositionOffset;
471                            anchorOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
472                        } else {
473                            final int target = mPrimaryOrientation.getStartAfterPadding() +
474                                    mPendingScrollPositionOffset;
475                            anchorOffset = target - mPrimaryOrientation.getDecoratedStart(child);
476                        }
477                    } else {
478                        final int startGap = mPrimaryOrientation.getDecoratedStart(child)
479                                - mPrimaryOrientation.getStartAfterPadding();
480                        final int endGap = mPrimaryOrientation.getEndAfterPadding() -
481                                mPrimaryOrientation.getDecoratedEnd(child);
482                        final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
483                        // Use regular anchor item, just offset the layout.
484                        anchorItemPosition = mShouldReverseLayout ? getLastChildPosition()
485                                : getFirstChildPosition();
486                        if (childSize > mPrimaryOrientation.getTotalSpace()) {
487                            // Item does not fit. Fix depending on layout direction.
488                            anchorOffset = layoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
489                                    : mPrimaryOrientation.getStartAfterPadding();
490                        } else if (startGap < 0) {
491                            anchorOffset = -startGap;
492                        } else if (endGap < 0) {
493                            anchorOffset = endGap;
494                        } else {
495                            // Nothing to do, just layout normal.
496                            anchorItemPosition = mShouldReverseLayout ? getLastChildPosition()
497                                    : getFirstChildPosition();
498                            anchorOffset = INVALID_OFFSET;
499                        }
500                    }
501                } else {
502                    // Child is not visible. Set anchor coordinate depending on in which direction
503                    // child will be visible.
504                    anchorItemPosition = mPendingScrollPosition;
505                    if (mPendingScrollPositionOffset == INVALID_OFFSET) {
506                        final int position = calculateScrollDirectionForPosition(
507                                anchorItemPosition);
508                        if (position == LAYOUT_START) {
509                            anchorOffset = mPrimaryOrientation.getStartAfterPadding();
510                            layoutFromEnd = false;
511                        } else {
512                            anchorOffset = mPrimaryOrientation.getEndAfterPadding();
513                            layoutFromEnd = true;
514                        }
515                    } else {
516                        if (layoutFromEnd) {
517                            anchorOffset = mPrimaryOrientation.getEndAfterPadding()
518                                    - mPendingScrollPositionOffset;
519                        } else {
520                            anchorOffset = mPrimaryOrientation.getStartAfterPadding()
521                                    + mPendingScrollPositionOffset;
522                        }
523                    }
524                    invalidateSpanOffsets = true;
525                }
526            } else {
527                anchorOffset = INVALID_OFFSET;
528                anchorItemPosition = mPendingScrollPosition;
529            }
530
531        } else {
532            // We don't recycle views out of adapter order. This way, we can rely on the first or
533            // last child as the anchor position.
534            anchorItemPosition = mShouldReverseLayout ? getLastChildPosition()
535                    : getFirstChildPosition();
536            anchorOffset = INVALID_OFFSET;
537        }
538        if (getChildCount() > 0 && (mPendingSavedState == null
539                || !mPendingSavedState.mHasSpanOffsets)) {
540            if (invalidateSpanOffsets) {
541                for (int i = 0; i < mSpanCount; i++) {
542                    // Scroll to position is set, clear.
543                    mSpans[i].clear();
544                    if (anchorOffset != INVALID_OFFSET) {
545                        mSpans[i].setLine(anchorOffset);
546                    }
547                }
548            } else {
549                for (int i = 0; i < mSpanCount; i++) {
550                    mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorOffset);
551                }
552                if (DEBUG) {
553                    for (int i = 0; i < mSpanCount; i++) {
554                        Log.d(TAG, "cached start-end lines for " + i + ":" +
555                                mSpans[i].mCachedStart + ":" + mSpans[i].mCachedEnd);
556                    }
557                }
558            }
559        }
560        mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
561        detachAndScrapAttachedViews(recycler);
562        // Layout start.
563        updateLayoutStateToFillStart(anchorItemPosition, state);
564        if (!layoutFromEnd) {
565            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
566        }
567        fill(recycler, mLayoutState, state);
568
569        // Layout end.
570        updateLayoutStateToFillEnd(anchorItemPosition, state);
571        if (layoutFromEnd) {
572            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
573        }
574        fill(recycler, mLayoutState, state);
575
576        if (getChildCount() > 0) {
577            if (mShouldReverseLayout) {
578                fixEndGap(recycler, state, true);
579                fixStartGap(recycler, state, false);
580            } else {
581                fixStartGap(recycler, state, true);
582                fixEndGap(recycler, state, false);
583            }
584        }
585
586        // TODO remove this once RecyclerView is updated not to require this.
587        final int removedChildCount = mChildrenToBeRemoved.size();
588        for (int i = removedChildCount - 1; i >= 0; i--) {
589            View view = mChildrenToBeRemoved.get(i);
590            removeView(view);
591        }
592        mChildrenToBeRemoved.clear();
593        mPendingScrollPosition = RecyclerView.NO_POSITION;
594        mPendingScrollPositionOffset = INVALID_OFFSET;
595        mLastLayoutFromEnd = layoutFromEnd;
596        mPendingSavedState = null; // we don't need this anymore
597    }
598
599    /**
600     * Checks if a child is assigned to the non-optimal span.
601     *
602     * @param startChildIndex Starts checking after this child, inclusive
603     * @param endChildIndex   Starts checking until this child, exclusive
604     * @return The first View that is assigned to the wrong span.
605     */
606    View hasGapsToFix(int startChildIndex, int endChildIndex) {
607        // quick reject
608        if (startChildIndex >= endChildIndex) {
609            return null;
610        }
611        final int firstChildIndex, childLimit;
612        final int nextSpanDiff = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
613
614        if (mShouldReverseLayout) {
615            firstChildIndex = endChildIndex - 1;
616            childLimit = startChildIndex - 1;
617        } else {
618            firstChildIndex = startChildIndex;
619            childLimit = endChildIndex;
620        }
621        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
622        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
623            View child = getChildAt(i);
624            final int start = mPrimaryOrientation.getDecoratedStart(child);
625            final int end = mPrimaryOrientation.getDecoratedEnd(child);
626            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
627            if (layoutParams.mFullSpan) {
628                continue; // quick reject
629            }
630            int nextSpanIndex = layoutParams.getSpanIndex() + nextSpanDiff;
631            while (nextSpanIndex >= 0 && nextSpanIndex < mSpanCount) {
632                Span nextSpan = mSpans[nextSpanIndex];
633                if (nextSpan.isEmpty(start, end)) {
634                    return child;
635                }
636                nextSpanIndex += nextSpanDiff;
637            }
638        }
639        // everything looks good
640        return null;
641    }
642
643    @Override
644    public boolean supportsPredictiveItemAnimations() {
645        return true;
646    }
647
648    private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
649            int heightSpec) {
650        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
651        LayoutParams lp = (LayoutParams) child.getLayoutParams();
652        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + insets.left,
653                lp.rightMargin + insets.right);
654        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + insets.top,
655                lp.bottomMargin + insets.bottom);
656        child.measure(widthSpec, heightSpec);
657    }
658
659    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
660        if (startInset == 0 && endInset == 0) {
661            return spec;
662        }
663        final int mode = View.MeasureSpec.getMode(spec);
664        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
665            return View.MeasureSpec.makeMeasureSpec(
666                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
667        }
668        return spec;
669    }
670
671    @Override
672    public void onRestoreInstanceState(Parcelable state) {
673        if (state instanceof SavedState) {
674            mPendingSavedState = (SavedState) state;
675            requestLayout();
676        } else if (DEBUG) {
677            Log.d(TAG, "invalid saved state class");
678        }
679    }
680
681    @Override
682    public Parcelable onSaveInstanceState() {
683        if (mPendingSavedState != null) {
684            return new SavedState(mPendingSavedState);
685        }
686        SavedState state = new SavedState();
687        state.mOrientation = mOrientation;
688        state.mReverseLayout = mReverseLayout;
689        state.mSpanCount = mSpanCount;
690        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
691        state.mGapStrategy = mGapStrategy;
692
693        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
694            state.mSpanLookup = mLazySpanLookup.mData;
695            state.mSpanLookupSize = state.mSpanLookup.length;
696        } else {
697            state.mSpanLookupSize = 0;
698        }
699
700        if (getChildCount() > 0) {
701            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
702                    : getFirstChildPosition();
703            state.mHasSpanOffsets = true;
704            state.mSpanOffsets = new int[mSpanCount];
705            for (int i = 0; i < mSpanCount; i++) {
706                state.mSpanOffsets[i] = mLastLayoutFromEnd ? mSpans[i].getEndLine()
707                        : mSpans[i].getStartLine();
708            }
709        } else {
710            state.mAnchorPosition = RecyclerView.NO_POSITION;
711            state.mHasSpanOffsets = false;
712        }
713        if (DEBUG) {
714            Log.d(TAG, "saved state:\n" + state);
715        }
716        return state;
717    }
718
719    private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
720            boolean canOffsetChildren) {
721        final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
722        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
723        int fixOffset;
724        if (gap > 0) {
725            fixOffset = -scrollBy(-gap, recycler, state);
726        } else {
727            return; // nothing to fix
728        }
729        gap -= fixOffset;
730        if (canOffsetChildren && gap > 0) {
731            mPrimaryOrientation.offsetChildren(gap);
732        }
733    }
734
735    private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
736            boolean canOffsetChildren) {
737        final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
738        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
739        int fixOffset;
740        if (gap > 0) {
741            fixOffset = scrollBy(gap, recycler, state);
742        } else {
743            return; // nothing to fix
744        }
745        gap -= fixOffset;
746        if (canOffsetChildren && gap > 0) {
747            mPrimaryOrientation.offsetChildren(-gap);
748        }
749    }
750
751    private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) {
752        mLayoutState.mAvailable = 0;
753        mLayoutState.mCurrentPosition = anchorPosition;
754        if (isSmoothScrolling()) {
755            final int targetPos = state.getTargetScrollPosition();
756            if (mShouldReverseLayout == targetPos < anchorPosition) {
757                mLayoutState.mExtra = 0;
758            } else {
759                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
760            }
761        } else {
762            mLayoutState.mExtra = 0;
763        }
764        mLayoutState.mLayoutDirection = LAYOUT_START;
765        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
766                : ITEM_DIRECTION_HEAD;
767    }
768
769    private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) {
770        mLayoutState.mAvailable = 0;
771        mLayoutState.mCurrentPosition = anchorPosition;
772        if (isSmoothScrolling()) {
773            final int targetPos = state.getTargetScrollPosition();
774            if (mShouldReverseLayout == targetPos > anchorPosition) {
775                mLayoutState.mExtra = 0;
776            } else {
777                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
778            }
779        } else {
780            mLayoutState.mExtra = 0;
781        }
782        mLayoutState.mLayoutDirection = LAYOUT_END;
783        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
784                : ITEM_DIRECTION_TAIL;
785    }
786
787    @Override
788    public void offsetChildrenHorizontal(int dx) {
789        super.offsetChildrenHorizontal(dx);
790        for (int i = 0; i < mSpanCount; i++) {
791            mSpans[i].onOffset(dx);
792        }
793    }
794
795    @Override
796    public void offsetChildrenVertical(int dy) {
797        super.offsetChildrenVertical(dy);
798        for (int i = 0; i < mSpanCount; i++) {
799            mSpans[i].onOffset(dy);
800        }
801    }
802
803    @Override
804    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
805        if (!considerSpanInvalidate(positionStart, itemCount)) {
806            // If positions are not invalidated, move span offsets.
807            mLazySpanLookup.offsetForRemoval(positionStart, itemCount);
808        }
809    }
810
811    @Override
812    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
813        if (!considerSpanInvalidate(positionStart, itemCount)) {
814            // If positions are not invalidated, move span offsets.
815            mLazySpanLookup.offsetForAddition(positionStart, itemCount);
816        }
817    }
818
819    /**
820     * Checks whether it should invalidate span assignments in response to an adapter change.
821     */
822    private boolean considerSpanInvalidate(int positionStart, int itemCount) {
823        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
824        if (positionStart + itemCount <= minPosition) {
825            return false;// nothing to update.
826        }
827        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
828        mLazySpanLookup.invalidateAfter(positionStart);
829        if (positionStart <= maxPosition) {
830            requestLayout();
831        }
832        return true;
833    }
834
835    private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
836            RecyclerView.State state) {
837        mRemainingSpans.set(0, mSpanCount, true);
838        // The target position we are trying to reach.
839        final int targetLine;
840
841        /*
842        * The line until which we can recycle, as long as we add views.
843        * Keep in mind, it is still the line in layout direction which means; to calculate the
844        * actual recycle line, we should subtract/add the size in orientation.
845        */
846        final int recycleLine;
847        // Line of the furthest row.
848        if (layoutState.mLayoutDirection == LAYOUT_END) {
849            // ignore padding for recycler
850            recycleLine = mPrimaryOrientation.getEnd() + mLayoutState.mAvailable;
851            targetLine = recycleLine + mLayoutState.mExtra;
852            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
853            for (int i = 0; i < mSpanCount; i++) {
854                final Span span = mSpans[i];
855                final int line = span.getEndLine(defaultLine);
856                if (line > targetLine) {
857                    mRemainingSpans.set(i, false);
858                }
859            }
860        } else { // LAYOUT_START
861            // ignore padding for recycler
862            recycleLine = - mLayoutState.mAvailable;
863            targetLine = recycleLine - mLayoutState.mExtra;
864            for (int i = 0; i < mSpanCount; i++) {
865                final Span span = mSpans[i];
866                final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
867                final int line = span.getStartLine(defaultLine);
868                if (line < targetLine) {
869                    mRemainingSpans.set(i, false);
870                }
871            }
872        }
873
874        final int widthSpec, heightSpec;
875        if (mOrientation == VERTICAL) {
876            widthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
877            heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
878        } else {
879            heightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
880            widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
881        }
882
883        while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
884            View view = layoutState.next(recycler);
885            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
886            if (layoutState.mLayoutDirection == LAYOUT_END) {
887                addView(view);
888            } else {
889                addView(view, 0);
890            }
891            if (lp.isItemRemoved()) {
892                mChildrenToBeRemoved.add(view);
893            }
894            if (lp.mFullSpan) {
895                final int fullSizeSpec = View.MeasureSpec.makeMeasureSpec(
896                        mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
897                if (mOrientation == VERTICAL) {
898                    measureChildWithDecorationsAndMargin(view, fullSizeSpec, heightSpec);
899                } else {
900                    measureChildWithDecorationsAndMargin(view, widthSpec, fullSizeSpec);
901                }
902            } else {
903                measureChildWithDecorationsAndMargin(view, widthSpec, heightSpec);
904            }
905
906            final int position = getPosition(view);
907            final int spanIndex = mLazySpanLookup.getSpan(position);
908            Span currentSpan;
909            if (spanIndex == LayoutParams.INVALID_SPAN_ID) {
910                if (lp.mFullSpan) {
911                    // assign full span items to first span
912                    currentSpan = mSpans[0];
913                } else {
914                    currentSpan = getNextSpan(layoutState);
915                }
916                mLazySpanLookup.setSpan(position, currentSpan);
917            } else {
918                currentSpan = mSpans[spanIndex];
919            }
920            final int start;
921            final int end;
922            if (layoutState.mLayoutDirection == LAYOUT_END) {
923                final int def = mShouldReverseLayout ? mPrimaryOrientation.getEndAfterPadding()
924                        : mPrimaryOrientation.getStartAfterPadding();
925                start = lp.mFullSpan ? getMaxEnd(def) : currentSpan.getEndLine(def);
926                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
927                if (lp.mFullSpan) {
928                    for (int i = 0; i < mSpanCount; i++) {
929                        mSpans[i].appendToSpan(view);
930                    }
931                } else {
932                    currentSpan.appendToSpan(view);
933                }
934            } else {
935                final int def = mShouldReverseLayout ? mPrimaryOrientation.getEndAfterPadding()
936                        : mPrimaryOrientation.getStartAfterPadding();
937                end = lp.mFullSpan ? getMinStart(def) : currentSpan.getStartLine(def);
938                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
939                if (lp.mFullSpan) {
940                    for (int i = 0; i < mSpanCount; i++) {
941                        mSpans[i].prependToSpan(view);
942                    }
943                } else {
944                    currentSpan.prependToSpan(view);
945                }
946
947            }
948            lp.mSpan = currentSpan;
949
950            if (DEBUG) {
951                Log.d(TAG, "adding view item " + lp.getViewPosition() + " between " + start + ","
952                        + end);
953            }
954
955            final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
956                    : currentSpan.mIndex * mSizePerSpan + mSecondaryOrientation
957                            .getStartAfterPadding();
958            final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
959            if (mOrientation == VERTICAL) {
960                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
961            } else {
962                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
963            }
964            if (lp.mFullSpan) {
965                for (int i = 0; i < mSpanCount; i++) {
966                    updateRemainingSpans(mSpans[i], mLayoutState.mLayoutDirection, targetLine);
967                }
968            } else {
969                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
970            }
971            if (mLayoutState.mLayoutDirection == LAYOUT_START) {
972                // calculate recycle line
973                int maxStart = getMaxStart(currentSpan.getStartLine());
974                recycleFromEnd(recycler, Math.max(recycleLine, maxStart)
975                        + mPrimaryOrientation.getTotalSpace());
976            } else {
977                // calculate recycle line
978                int minEnd = getMinEnd(currentSpan.getEndLine());
979                recycleFromStart(recycler, Math.min(recycleLine, minEnd)
980                        - mPrimaryOrientation.getTotalSpace());
981            }
982        }
983        if (DEBUG) {
984            Log.d(TAG, "fill, " + getChildCount());
985        }
986        if (mLayoutState.mLayoutDirection == LAYOUT_START) {
987            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
988            return Math.max(0, mLayoutState.mAvailable + (targetLine - minStart));
989        } else {
990            final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
991            return Math.max(0, mLayoutState.mAvailable + (max - targetLine));
992        }
993    }
994
995    private void layoutDecoratedWithMargins(View child, int left, int top, int right,
996            int bottom) {
997        LayoutParams lp = (LayoutParams) child.getLayoutParams();
998        layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
999                , bottom - lp.bottomMargin);
1000    }
1001
1002    private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
1003        final int deletedSize = span.getDeletedSize();
1004        if (layoutDir == LAYOUT_START) {
1005            final int line = span.getStartLine();
1006            if (line + deletedSize < targetLine) {
1007                mRemainingSpans.set(span.mIndex, false);
1008            }
1009        } else {
1010            final int line = span.getEndLine();
1011            if (line - deletedSize > targetLine) {
1012                mRemainingSpans.set(span.mIndex, false);
1013            }
1014        }
1015    }
1016
1017    private int getMaxStart(int def) {
1018        int maxStart = mSpans[0].getStartLine(def);
1019        for (int i = 1; i < mSpanCount; i++) {
1020            final int spanStart = mSpans[i].getStartLine(def);
1021            if (spanStart > maxStart) {
1022                maxStart = spanStart;
1023            }
1024        }
1025        return maxStart;
1026    }
1027
1028    private int getMinStart(int def) {
1029        int minStart = mSpans[0].getStartLine(def);
1030        for (int i = 1; i < mSpanCount; i++) {
1031            final int spanStart = mSpans[i].getStartLine(def);
1032            if (spanStart < minStart) {
1033                minStart = spanStart;
1034            }
1035        }
1036        return minStart;
1037    }
1038
1039    private int getMaxEnd(int def) {
1040        int maxEnd = mSpans[0].getEndLine(def);
1041        for (int i = 1; i < mSpanCount; i++) {
1042            final int spanEnd = mSpans[i].getEndLine(def);
1043            if (spanEnd > maxEnd) {
1044                maxEnd = spanEnd;
1045            }
1046        }
1047        return maxEnd;
1048    }
1049
1050    private int getMinEnd(int def) {
1051        int minEnd = mSpans[0].getEndLine(def);
1052        for (int i = 1; i < mSpanCount; i++) {
1053            final int spanEnd = mSpans[i].getEndLine(def);
1054            if (spanEnd < minEnd) {
1055                minEnd = spanEnd;
1056            }
1057        }
1058        return minEnd;
1059    }
1060
1061    private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
1062        if (DEBUG) {
1063            Log.d(TAG, "recycling from start for line " + line);
1064        }
1065        while (getChildCount() > 0) {
1066            View child = getChildAt(0);
1067            if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
1068                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1069                if (lp.mFullSpan) {
1070                    for (int j = 0; j < mSpanCount; j++) {
1071                        mSpans[j].popStart();
1072                    }
1073                } else {
1074                    lp.mSpan.popStart();
1075                }
1076                removeAndRecycleView(child, recycler);
1077            } else {
1078                return;// done
1079            }
1080        }
1081    }
1082
1083    private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
1084        final int childCount = getChildCount();
1085        int i;
1086        for (i = childCount - 1; i >= 0; i--) {
1087            View child = getChildAt(i);
1088            if (mPrimaryOrientation.getDecoratedStart(child) > line) {
1089                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1090                if (lp.mFullSpan) {
1091                    for (int j = 0; j < mSpanCount; j++) {
1092                        mSpans[j].popEnd();
1093                    }
1094                } else {
1095                    lp.mSpan.popEnd();
1096                }
1097                removeAndRecycleView(child, recycler);
1098            } else {
1099                return;// done
1100            }
1101        }
1102    }
1103
1104    /**
1105     * Finds the span for the next view.
1106     */
1107    private Span getNextSpan(LayoutState layoutState) {
1108        final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL();
1109        if (layoutState.mLayoutDirection == LAYOUT_END) {
1110            Span min = mSpans[0];
1111            int minLine = min.getEndLine(mPrimaryOrientation.getStartAfterPadding());
1112            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
1113            for (int i = 1; i < mSpanCount; i++) {
1114                final Span other = mSpans[i];
1115                final int otherLine = other.getEndLine(defaultLine);
1116                if (otherLine < minLine || (otherLine == minLine && preferLastSpan)) {
1117                    min = other;
1118                    minLine = otherLine;
1119                }
1120            }
1121            return min;
1122        } else {
1123            Span max = mSpans[0];
1124            int maxLine = max.getStartLine(mPrimaryOrientation.getEndAfterPadding());
1125            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
1126            for (int i = 1; i < mSpanCount; i++) {
1127                final Span other = mSpans[i];
1128                final int otherLine = other.getStartLine(defaultLine);
1129                if (otherLine > maxLine || (otherLine == maxLine && !preferLastSpan)) {
1130                    max = other;
1131                    maxLine = otherLine;
1132                }
1133            }
1134            return max;
1135        }
1136    }
1137
1138    @Override
1139    public boolean canScrollVertically() {
1140        return mOrientation == VERTICAL;
1141    }
1142
1143    @Override
1144    public boolean canScrollHorizontally() {
1145        return mOrientation == HORIZONTAL;
1146    }
1147
1148    @Override
1149    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
1150            RecyclerView.State state) {
1151        return scrollBy(dx, recycler, state);
1152    }
1153
1154    @Override
1155    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
1156            RecyclerView.State state) {
1157        return scrollBy(dy, recycler, state);
1158    }
1159
1160    private int calculateScrollDirectionForPosition(int position) {
1161        if (getChildCount() == 0) {
1162            return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
1163        }
1164        final int firstChildPos = getFirstChildPosition();
1165        return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
1166    }
1167
1168    @Override
1169    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
1170            int position) {
1171        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
1172            @Override
1173            public PointF computeScrollVectorForPosition(int targetPosition) {
1174                final int direction = calculateScrollDirectionForPosition(targetPosition);
1175                if (direction == 0) {
1176                    return null;
1177                }
1178                if (mOrientation == HORIZONTAL) {
1179                    return new PointF(direction, 0);
1180                } else {
1181                    return new PointF(0, direction);
1182                }
1183            }
1184        };
1185        scroller.setTargetPosition(position);
1186        startSmoothScroll(scroller);
1187    }
1188
1189    @Override
1190    public void scrollToPosition(int position) {
1191        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
1192            mPendingSavedState.invalidateAnchorPositionInfo();
1193        }
1194        mPendingScrollPosition = position;
1195        mPendingScrollPositionOffset = INVALID_OFFSET;
1196        requestLayout();
1197    }
1198
1199    /**
1200     * Scroll to the specified adapter position with the given offset from layout start.
1201     * <p>
1202     * Note that scroll position change will not be reflected until the next layout call.
1203     * <p>
1204     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
1205     *
1206     * @param position Index (starting at 0) of the reference item.
1207     * @param offset   The distance (in pixels) between the start edge of the item view and
1208     *                 start edge of the RecyclerView.
1209     * @see #setReverseLayout(boolean)
1210     * @see #scrollToPosition(int)
1211     */
1212    public void scrollToPositionWithOffset(int position, int offset) {
1213        if (mPendingSavedState != null) {
1214            mPendingSavedState.invalidateAnchorPositionInfo();
1215        }
1216        mPendingScrollPosition = position;
1217        mPendingScrollPositionOffset = offset;
1218        requestLayout();
1219    }
1220
1221    private int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
1222        ensureOrientationHelper();
1223        final int referenceChildPosition;
1224        if (dt > 0) { // layout towards end
1225            mLayoutState.mLayoutDirection = LAYOUT_END;
1226            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
1227                    : ITEM_DIRECTION_TAIL;
1228            referenceChildPosition = getLastChildPosition();
1229        } else {
1230            mLayoutState.mLayoutDirection = LAYOUT_START;
1231            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
1232                    : ITEM_DIRECTION_HEAD;
1233            referenceChildPosition = getFirstChildPosition();
1234        }
1235        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
1236        final int absDt = Math.abs(dt);
1237        mLayoutState.mAvailable = absDt;
1238        mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
1239        int consumed = fill(recycler, mLayoutState, state);
1240        final int totalScroll;
1241        if (absDt < consumed) {
1242            totalScroll = dt;
1243        } else if (dt < 0) {
1244            totalScroll = -consumed;
1245        } else { // dt > 0
1246            totalScroll = consumed;
1247        }
1248        if (DEBUG) {
1249            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
1250        }
1251
1252        if (mGapStrategy == GAP_HANDLING_LAZY
1253                && mLayoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
1254            final int targetStart = mPrimaryOrientation.getStartAfterPadding();
1255            final int targetEnd = mPrimaryOrientation.getEndAfterPadding();
1256            lazyOffsetSpans(-totalScroll, targetStart, targetEnd);
1257        } else {
1258            mPrimaryOrientation.offsetChildren(-totalScroll);
1259        }
1260        // always reset this if we scroll for a proper save instance state
1261        mLastLayoutFromEnd = mShouldReverseLayout;
1262
1263        if (totalScroll != 0 && mGapStrategy != GAP_HANDLING_NONE
1264                && mLayoutState.mItemDirection == ITEM_DIRECTION_HEAD && !mHasGaps) {
1265            final int addedChildCount = Math.abs(mLayoutState.mCurrentPosition
1266                    - (referenceChildPosition + mLayoutState.mItemDirection));
1267            if (addedChildCount > 0) {
1268                // check if any child has been attached to wrong span. If so, trigger a re-layout
1269                // after scroll
1270                final View viewInWrongSpan;
1271                final View referenceView = findViewByPosition(referenceChildPosition);
1272                if (referenceView == null) {
1273                    viewInWrongSpan = hasGapsToFix(0, getChildCount());
1274                } else {
1275                    if (mLayoutState.mLayoutDirection == LAYOUT_START) {
1276                        viewInWrongSpan = hasGapsToFix(0, addedChildCount);
1277                    } else {
1278                        viewInWrongSpan = hasGapsToFix(getChildCount() - addedChildCount,
1279                                getChildCount());
1280                    }
1281                }
1282                mHasGaps = viewInWrongSpan != null;
1283            }
1284        }
1285        return totalScroll;
1286    }
1287
1288    /**
1289     * The actual method that implements {@link #GAP_HANDLING_LAZY}
1290     */
1291    private void lazyOffsetSpans(int offset, int targetStart, int targetEnd) {
1292        // For each span offset children one by one.
1293        // When a fullSpan item is reached, stop and wait for other spans to reach to that span.
1294        // When all reach, offset fullSpan to max of others and continue.
1295        int childrenToOffset = getChildCount();
1296        int[] indexPerSpan = new int[mSpanCount];
1297        int[] offsetPerSpan = new int[mSpanCount];
1298
1299        final int childOrder = offset > 0 ? ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
1300        if (offset > 0) {
1301            Arrays.fill(indexPerSpan, 0);
1302        } else {
1303            for (int i = 0; i < mSpanCount; i++) {
1304                indexPerSpan[i] = mSpans[i].mViews.size() - 1;
1305            }
1306        }
1307
1308        for (int i = 0; i < mSpanCount; i++) {
1309            offsetPerSpan[i] = mSpans[i].getNormalizedOffset(offset, targetStart, targetEnd);
1310        }
1311        if (DEBUG) {
1312            Log.d(TAG, "lazy offset start. normalized: " + Arrays.toString(offsetPerSpan));
1313        }
1314
1315        while (childrenToOffset > 0) {
1316            View fullSpanView = null;
1317            for (int spanIndex = 0; spanIndex < mSpanCount; spanIndex++) {
1318                Span span = mSpans[spanIndex];
1319                int viewIndex;
1320                for (viewIndex = indexPerSpan[spanIndex];
1321                        viewIndex < span.mViews.size() && viewIndex >= 0; viewIndex += childOrder) {
1322                    View view = span.mViews.get(viewIndex);
1323                    if (DEBUG) {
1324                        Log.d(TAG, "span " + spanIndex + ", view:" + viewIndex + ", pos:"
1325                                + getPosition(view));
1326                    }
1327                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
1328                    if (lp.mFullSpan) {
1329                        if (DEBUG) {
1330                            Log.d(TAG, "stopping on full span view on index " + viewIndex
1331                                    + " in span " + spanIndex);
1332                        }
1333                        fullSpanView = view;
1334                        viewIndex += childOrder;// move to next view
1335                        break;
1336                    }
1337                    // offset this child normally
1338                    mPrimaryOrientation.offsetChild(view, offsetPerSpan[spanIndex]);
1339                    final int nextChildIndex = viewIndex + childOrder;
1340                    if (nextChildIndex < span.mViews.size() && nextChildIndex >= 0) {
1341                        View nextView = span.mViews.get(nextChildIndex);
1342                        // find gap between, before offset
1343                        if (childOrder == ITEM_DIRECTION_HEAD) {// negative
1344                            offsetPerSpan[spanIndex] = Math
1345                                    .min(0, mPrimaryOrientation.getDecoratedStart(view)
1346                                            - mPrimaryOrientation.getDecoratedEnd(nextView));
1347                        } else {
1348                            offsetPerSpan[spanIndex] = Math
1349                                    .max(0, mPrimaryOrientation.getDecoratedEnd(view) -
1350                                            mPrimaryOrientation.getDecoratedStart(nextView));
1351                        }
1352                        if (DEBUG) {
1353                            Log.d(TAG, "offset diff:" + offsetPerSpan[spanIndex] + " between "
1354                                    + getPosition(nextView) + " and " + getPosition(view));
1355                        }
1356                    }
1357                    childrenToOffset--;
1358                }
1359                indexPerSpan[spanIndex] = viewIndex;
1360            }
1361            if (fullSpanView != null) {
1362                // we have to offset this view. We'll offset it as the biggest amount necessary
1363                int winnerSpan = 0;
1364                int winnerSpanOffset = Math.abs(offsetPerSpan[winnerSpan]);
1365                for (int i = 1; i < mSpanCount; i++) {
1366                    final int spanOffset = Math.abs(offsetPerSpan[i]);
1367                    if (spanOffset > winnerSpanOffset) {
1368                        winnerSpan = i;
1369                        winnerSpanOffset = spanOffset;
1370                    }
1371                }
1372                if (DEBUG) {
1373                    Log.d(TAG, "winner offset:" + offsetPerSpan[winnerSpan] + " of " + winnerSpan);
1374                }
1375                mPrimaryOrientation.offsetChild(fullSpanView, offsetPerSpan[winnerSpan]);
1376                childrenToOffset--;
1377
1378                for (int spanIndex = 0; spanIndex < mSpanCount; spanIndex++) {
1379                    final int nextViewIndex = indexPerSpan[spanIndex];
1380                    final Span span = mSpans[spanIndex];
1381                    if (nextViewIndex < span.mViews.size() && nextViewIndex > 0) {
1382                        View nextView = span.mViews.get(nextViewIndex);
1383                        // find gap between, before offset
1384                        if (childOrder == ITEM_DIRECTION_HEAD) {// negative
1385                            offsetPerSpan[spanIndex] = Math
1386                                    .min(0, mPrimaryOrientation.getDecoratedStart(fullSpanView)
1387                                            - mPrimaryOrientation.getDecoratedEnd(nextView));
1388                        } else {
1389                            offsetPerSpan[spanIndex] = Math
1390                                    .max(0, mPrimaryOrientation.getDecoratedEnd(fullSpanView) -
1391                                            mPrimaryOrientation.getDecoratedStart(nextView));
1392                        }
1393                    }
1394                }
1395            }
1396        }
1397        for (int spanIndex = 0; spanIndex < mSpanCount; spanIndex++) {
1398            mSpans[spanIndex].invalidateCache();
1399        }
1400    }
1401
1402    private int getLastChildPosition() {
1403        final int childCount = getChildCount();
1404        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
1405    }
1406
1407    private int getFirstChildPosition() {
1408        final int childCount = getChildCount();
1409        return childCount == 0 ? 0 : getPosition(getChildAt(0));
1410    }
1411
1412    @Override
1413    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1414        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1415                ViewGroup.LayoutParams.WRAP_CONTENT);
1416    }
1417
1418    @Override
1419    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
1420        return new LayoutParams(c, attrs);
1421    }
1422
1423    @Override
1424    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1425        if (lp instanceof ViewGroup.MarginLayoutParams) {
1426            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
1427        } else {
1428            return new LayoutParams(lp);
1429        }
1430    }
1431
1432    @Override
1433    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
1434        return lp instanceof LayoutParams;
1435    }
1436
1437    public int getOrientation() {
1438        return mOrientation;
1439    }
1440
1441
1442    /**
1443     * LayoutParams used by StaggeredGridLayoutManager.
1444     */
1445    public static class LayoutParams extends RecyclerView.LayoutParams {
1446
1447        /**
1448         * Span Id for Views that are not laid out yet.
1449         */
1450        public static final int INVALID_SPAN_ID = -1;
1451
1452        // Package scope to be able to access from tests.
1453        Span mSpan;
1454
1455        boolean mFullSpan;
1456
1457        public LayoutParams(Context c, AttributeSet attrs) {
1458            super(c, attrs);
1459        }
1460
1461        public LayoutParams(int width, int height) {
1462            super(width, height);
1463        }
1464
1465        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1466            super(source);
1467        }
1468
1469        public LayoutParams(ViewGroup.LayoutParams source) {
1470            super(source);
1471        }
1472
1473        public LayoutParams(RecyclerView.LayoutParams source) {
1474            super(source);
1475        }
1476
1477        /**
1478         * When set to true, the item will layout using all span area. That means, if orientation
1479         * is vertical, the view will have full width; if orientation is horizontal, the view will
1480         * have full height.
1481         *
1482         * @param fullSpan True if this item should traverse all spans.
1483         */
1484        public void setFullSpan(boolean fullSpan) {
1485            mFullSpan = fullSpan;
1486        }
1487
1488        /**
1489         * Returns the Span index to which this View is assigned.
1490         *
1491         * @return The Span index of the View. If View is not yet assigned to any span, returns
1492         * {@link #INVALID_SPAN_ID}.
1493         */
1494        public final int getSpanIndex() {
1495            if (mSpan == null) {
1496                return INVALID_SPAN_ID;
1497            }
1498            return mSpan.mIndex;
1499        }
1500    }
1501
1502    // Package scoped to access from tests.
1503    class Span {
1504
1505        final int INVALID_LINE = Integer.MIN_VALUE;
1506
1507        private ArrayList<View> mViews = new ArrayList<View>();
1508
1509        int mCachedStart = INVALID_LINE;
1510
1511        int mCachedEnd = INVALID_LINE;
1512
1513        int mDeletedSize = 0;
1514
1515        final int mIndex;
1516
1517        private Span(int index) {
1518            mIndex = index;
1519        }
1520
1521        int getStartLine(int def) {
1522            if (mCachedStart != INVALID_LINE) {
1523                return mCachedStart;
1524            }
1525            if (mViews.size() == 0) {
1526                return def;
1527            }
1528            mCachedStart = mPrimaryOrientation.getDecoratedStart(mViews.get(0));
1529            return mCachedStart;
1530        }
1531
1532        // Use this one when default value does not make sense and not having a value means a bug.
1533        int getStartLine() {
1534            if (mCachedStart != INVALID_LINE) {
1535                return mCachedStart;
1536            }
1537            mCachedStart = mPrimaryOrientation.getDecoratedStart(mViews.get(0));
1538            return mCachedStart;
1539        }
1540
1541        int getEndLine(int def) {
1542            if (mCachedEnd != INVALID_LINE) {
1543                return mCachedEnd;
1544            }
1545            final int size = mViews.size();
1546            if (size == 0) {
1547                return def;
1548            }
1549            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(mViews.get(size - 1));
1550            return mCachedEnd;
1551        }
1552
1553        // Use this one when default value does not make sense and not having a value means a bug.
1554        int getEndLine() {
1555            if (mCachedEnd != INVALID_LINE) {
1556                return mCachedEnd;
1557            }
1558            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(mViews.get(mViews.size() - 1));
1559            return mCachedEnd;
1560        }
1561
1562        void prependToSpan(View view) {
1563            LayoutParams lp = getLayoutParams(view);
1564            lp.mSpan = this;
1565            mViews.add(0, view);
1566            mCachedStart = INVALID_LINE;
1567            if (mViews.size() == 1) {
1568                mCachedEnd = INVALID_LINE;
1569            }
1570            if (lp.isItemRemoved()) {
1571                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
1572            }
1573        }
1574
1575        void appendToSpan(View view) {
1576            LayoutParams lp = getLayoutParams(view);
1577            lp.mSpan = this;
1578            mViews.add(view);
1579            mCachedEnd = INVALID_LINE;
1580            if (mViews.size() == 1) {
1581                mCachedStart = INVALID_LINE;
1582            }
1583            if (lp.isItemRemoved()) {
1584                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
1585            }
1586        }
1587
1588        // Useful method to preserve positions on a re-layout.
1589        void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
1590            int reference;
1591            if (reverseLayout) {
1592                reference = getEndLine(INVALID_LINE);
1593            } else {
1594                reference = getStartLine(INVALID_LINE);
1595            }
1596            clear();
1597            if (reference == INVALID_LINE) {
1598                return;
1599            }
1600            if (offset != INVALID_OFFSET) {
1601                reference += offset;
1602            }
1603            mCachedStart = mCachedEnd = reference;
1604        }
1605
1606        void clear() {
1607            mViews.clear();
1608            invalidateCache();
1609            mDeletedSize = 0;
1610        }
1611
1612        void invalidateCache() {
1613            mCachedStart = INVALID_LINE;
1614            mCachedEnd = INVALID_LINE;
1615        }
1616
1617        void setLine(int line) {
1618            mCachedEnd = mCachedStart = line;
1619        }
1620
1621        void popEnd() {
1622            final int size = mViews.size();
1623            View end = mViews.remove(size - 1);
1624            final LayoutParams lp = getLayoutParams(end);
1625            lp.mSpan = null;
1626            if (lp.isItemRemoved()) {
1627                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
1628            }
1629            if (size == 1) {
1630                mCachedStart = INVALID_LINE;
1631            }
1632            mCachedEnd = INVALID_LINE;
1633        }
1634
1635        void popStart() {
1636            View start = mViews.remove(0);
1637            final LayoutParams lp = getLayoutParams(start);
1638            lp.mSpan = null;
1639            if (mViews.size() == 0) {
1640                mCachedEnd = INVALID_LINE;
1641            }
1642            if (lp.isItemRemoved()) {
1643                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
1644            }
1645            mCachedStart = INVALID_LINE;
1646        }
1647
1648        // TODO cache this.
1649        public int getDeletedSize() {
1650            return mDeletedSize;
1651        }
1652
1653        LayoutParams getLayoutParams(View view) {
1654            return (LayoutParams) view.getLayoutParams();
1655        }
1656
1657        void onOffset(int dt) {
1658            if (mCachedStart != INVALID_LINE) {
1659                mCachedStart += dt;
1660            }
1661            if (mCachedEnd != INVALID_LINE) {
1662                mCachedEnd += dt;
1663            }
1664        }
1665
1666        // normalized offset is how much this span can scroll
1667        int getNormalizedOffset(int dt, int targetStart, int targetEnd) {
1668            if (mViews.size() == 0) {
1669                return 0;
1670            }
1671            if (dt < 0) {
1672                final int endSpace = getEndLine() - targetEnd;
1673                if (endSpace <= 0) {
1674                    return 0;
1675                }
1676                return -dt > endSpace ? -endSpace : dt;
1677            } else {
1678                final int startSpace = targetStart - getStartLine();
1679                if (startSpace <= 0) {
1680                    return 0;
1681                }
1682                return startSpace < dt ? startSpace : dt;
1683            }
1684        }
1685
1686        /**
1687         * Returns if there is no child between start-end lines
1688         *
1689         * @param start The start line
1690         * @param end   The end line
1691         * @return true if a new child can be added between start and end
1692         */
1693        boolean isEmpty(int start, int end) {
1694            final int count = mViews.size();
1695            for (int i = 0; i < count; i++) {
1696                final View view = mViews.get(i);
1697                if (mPrimaryOrientation.getDecoratedStart(view) < end &&
1698                        mPrimaryOrientation.getDecoratedEnd(view) > start) {
1699                    return false;
1700                }
1701            }
1702            return true;
1703        }
1704    }
1705
1706    /**
1707     * An array of mappings from adapter position to span.
1708     * This only grows when a write happens and it grows up to the size of the adapter.
1709     */
1710    static class LazySpanLookup {
1711
1712        private static final int MIN_SIZE = 10;
1713
1714        int[] mData;
1715
1716        int mAdapterSize; // we don't want to grow beyond that, unless it grows
1717
1718        void invalidateAfter(int position) {
1719            if (mData == null) {
1720                return;
1721            }
1722            if (position >= mData.length) {
1723                return;
1724            }
1725            Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
1726        }
1727
1728        int getSpan(int position) {
1729            if (mData == null || position >= mData.length) {
1730                return LayoutParams.INVALID_SPAN_ID;
1731            } else {
1732                return mData[position];
1733            }
1734        }
1735
1736        void setSpan(int position, Span span) {
1737            ensureSize(position);
1738            mData[position] = span.mIndex;
1739        }
1740
1741        int sizeForPosition(int position) {
1742            int len = mData.length;
1743            while (len <= position) {
1744                len *= 2;
1745            }
1746            if (len > mAdapterSize) {
1747                len = mAdapterSize;
1748            }
1749            return len;
1750        }
1751
1752        void ensureSize(int position) {
1753            if (mData == null) {
1754                mData = new int[Math.max(position, MIN_SIZE) + 1];
1755                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
1756            } else if (position >= mData.length) {
1757                int[] old = mData;
1758                mData = new int[sizeForPosition(position)];
1759                System.arraycopy(old, 0, mData, 0, old.length);
1760                Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
1761            }
1762        }
1763
1764        void clear() {
1765            if (mData != null) {
1766                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
1767            }
1768        }
1769
1770        void offsetForRemoval(int positionStart, int itemCount) {
1771            ensureSize(positionStart + itemCount);
1772            System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
1773                    mData.length - positionStart - itemCount);
1774            Arrays.fill(mData, mData.length - itemCount, mData.length,
1775                    LayoutParams.INVALID_SPAN_ID);
1776        }
1777
1778        void offsetForAddition(int positionStart, int itemCount) {
1779            ensureSize(positionStart + itemCount);
1780            System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
1781                    mData.length - positionStart - itemCount);
1782            Arrays.fill(mData, positionStart, positionStart + itemCount,
1783                    LayoutParams.INVALID_SPAN_ID);
1784        }
1785    }
1786
1787    static class SavedState implements Parcelable {
1788
1789        int mOrientation;
1790
1791        int mSpanCount;
1792
1793        int mGapStrategy;
1794
1795        int mAnchorPosition;
1796
1797        int[] mSpanOffsets;
1798
1799        int mSpanLookupSize;
1800
1801        int[] mSpanLookup;
1802
1803        boolean mReverseLayout;
1804
1805        boolean mAnchorLayoutFromEnd;
1806
1807        boolean mHasSpanOffsets;
1808
1809        public SavedState() {
1810        }
1811
1812        SavedState(Parcel in) {
1813            mOrientation = in.readInt();
1814            mSpanCount = in.readInt();
1815            mGapStrategy = in.readInt();
1816            mAnchorPosition = in.readInt();
1817            mHasSpanOffsets = in.readInt() == 1;
1818            if (mHasSpanOffsets) {
1819                mSpanOffsets = new int[mSpanCount];
1820                in.readIntArray(mSpanOffsets);
1821            }
1822
1823            mSpanLookupSize = in.readInt();
1824            if (mSpanLookupSize > 0) {
1825                mSpanLookup = new int[mSpanLookupSize];
1826                in.readIntArray(mSpanLookup);
1827            }
1828            mReverseLayout = in.readInt() == 1;
1829            mAnchorLayoutFromEnd = in.readInt() == 1;
1830        }
1831
1832        public SavedState(SavedState other) {
1833            mOrientation = other.mOrientation;
1834            mSpanCount = other.mSpanCount;
1835            mGapStrategy = other.mGapStrategy;
1836            mAnchorPosition = other.mAnchorPosition;
1837            mHasSpanOffsets = other.mHasSpanOffsets;
1838            mSpanOffsets = other.mSpanOffsets;
1839            mSpanLookupSize = other.mSpanLookupSize;
1840            mSpanLookup = other.mSpanLookup;
1841            mReverseLayout = other.mReverseLayout;
1842            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
1843        }
1844
1845        void invalidateSpanInfo() {
1846            mSpanOffsets = null;
1847            mHasSpanOffsets = false;
1848            mSpanCount = -1;
1849            mSpanLookupSize = 0;
1850            mSpanLookup = null;
1851        }
1852
1853        void invalidateAnchorPositionInfo() {
1854            mSpanOffsets = null;
1855            mHasSpanOffsets = false;
1856            mAnchorPosition = RecyclerView.NO_POSITION;
1857        }
1858
1859        @Override
1860        public int describeContents() {
1861            return 0;
1862        }
1863
1864        @Override
1865        public void writeToParcel(Parcel dest, int flags) {
1866            dest.writeInt(mOrientation);
1867            dest.writeInt(mSpanCount);
1868            dest.writeInt(mGapStrategy);
1869            dest.writeInt(mAnchorPosition);
1870            dest.writeInt(mHasSpanOffsets ? 1 : 0);
1871            if (mHasSpanOffsets) {
1872                dest.writeIntArray(mSpanOffsets);
1873            }
1874            dest.writeInt(mSpanLookupSize);
1875            if (mSpanLookupSize > 0) {
1876                dest.writeIntArray(mSpanLookup);
1877            }
1878            dest.writeInt(mReverseLayout ? 1 : 0);
1879            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
1880        }
1881
1882        @Override
1883        public String toString() {
1884            return "SavedState{" +
1885                    "mOrientation=" + mOrientation +
1886                    ", mSpanCount=" + mSpanCount +
1887                    ", mGapStrategy=" + mGapStrategy +
1888                    ", mAnchorPosition=" + mAnchorPosition +
1889                    ", mSpanOffsets=" + Arrays.toString(mSpanOffsets) +
1890                    ", mSpanLookupSize=" + mSpanLookupSize +
1891                    ", mSpanLookup=" + Arrays.toString(mSpanLookup) +
1892                    ", mReverseLayout=" + mReverseLayout +
1893                    ", mAnchorLayoutFromEnd=" + mAnchorLayoutFromEnd +
1894                    ", mHasSpanOffsets=" + mHasSpanOffsets +
1895                    '}';
1896        }
1897
1898        public static final Parcelable.Creator<SavedState> CREATOR
1899                = new Parcelable.Creator<SavedState>() {
1900            @Override
1901            public SavedState createFromParcel(Parcel in) {
1902                return new SavedState(in);
1903            }
1904
1905            @Override
1906            public SavedState[] newArray(int size) {
1907                return new SavedState[size];
1908            }
1909        };
1910    }
1911}
1912