FileDocCategorySizeDatePackage
StaggeredGridLayoutManager.javaAPI DocAndroid 5.1 API104273Thu Mar 12 22:22:56 GMT 2015android.support.v7.widget

StaggeredGridLayoutManager

public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager
A LayoutManager that lays out children in a staggered grid formation. It supports horizontal & vertical layout as well as an ability to layout children in reverse.

Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps, StaggeredGridLayoutManager can offset spans independently or move items between spans. You can control this behavior via {@link #setGapStrategy(int)}.

Fields Summary
public static final String
TAG
private static final boolean
DEBUG
public static final int
HORIZONTAL
public static final int
VERTICAL
public static final int
GAP_HANDLING_NONE
Does not do anything to hide gaps.
public static final int
GAP_HANDLING_LAZY
public static final int
GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will check if there are gaps in the because of full span items. If it finds, it will re-layout and move items to correct positions with animations.

For example, if LayoutManager ends up with the following layout due to adapter changes:

AAA
_BC
DDD

It will animate to the following state:

AAA
BC_
DDD
private static final int
INVALID_OFFSET
private int
mSpanCount
Number of spans
private Span[]
mSpans
OrientationHelper
mPrimaryOrientation
Primary orientation is the layout's orientation, secondary orientation is the orientation for spans. Having both makes code much cleaner for calculations.
OrientationHelper
mSecondaryOrientation
private int
mOrientation
private int
mSizePerSpan
The width or height per span, depending on the orientation.
private LayoutState
mLayoutState
private boolean
mReverseLayout
boolean
mShouldReverseLayout
Aggregated reverse layout value that takes RTL into account.
private BitSet
mRemainingSpans
Temporary variable used during fill method to check which spans needs to be filled.
int
mPendingScrollPosition
When LayoutManager needs to scroll to a position, it sets this variable and requests a layout which will check this variable and re-layout accordingly.
int
mPendingScrollPositionOffset
Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is called.
LazySpanLookup
mLazySpanLookup
Keeps the mapping between the adapter positions and spans. This is necessary to provide a consistent experience when user scrolls the list.
private int
mGapStrategy
how we handle gaps in UI.
private boolean
mLastLayoutFromEnd
Saved state needs this information to properly layout on restore.
private boolean
mLastLayoutRTL
Saved state and onLayout needs this information to re-layout properly
private SavedState
mPendingSavedState
SavedState is not handled until a layout happens. This is where we keep it until next layout.
private int
mFullSizeSpec
Re-used measurement specs. updated by onLayout.
private int
mWidthSpec
private int
mHeightSpec
private final AnchorInfo
mAnchorInfo
Re-used anchor info.
private boolean
mLaidOutInvalidFullSpan
If a full span item is invalid / or created in reverse direction; it may create gaps in the UI. While laying out, if such case is detected, we set this flag.

After scrolling stops, we check this flag and if it is set, re-layout.

private boolean
mSmoothScrollbarEnabled
Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
private final Runnable
checkForGapsRunnable
Constructors Summary
public StaggeredGridLayoutManager(int spanCount, int orientation)
Creates a StaggeredGridLayoutManager with given parameters.

param
spanCount If orientation is vertical, spanCount is number of columns. If orientation is horizontal, spanCount is number of rows.
param
orientation {@link #VERTICAL} or {@link #HORIZONTAL}


                                                               
         
        mOrientation = orientation;
        setSpanCount(spanCount);
    
Methods Summary
private voidappendViewToAllSpans(android.view.View view)

        // traverse in reverse so that we end up assigning full span items to 0
        for (int i = mSpanCount - 1; i >= 0; i--) {
            mSpans[i].appendToSpan(view);
        }
    
private voidapplyPendingSavedState(android.support.v7.widget.StaggeredGridLayoutManager$AnchorInfo anchorInfo)

        if (DEBUG) {
            Log.d(TAG, "found saved state: " + mPendingSavedState);
        }
        if (mPendingSavedState.mSpanOffsetsSize > 0) {
            if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
                for (int i = 0; i < mSpanCount; i++) {
                    mSpans[i].clear();
                    int line = mPendingSavedState.mSpanOffsets[i];
                    if (line != Span.INVALID_LINE) {
                        if (mPendingSavedState.mAnchorLayoutFromEnd) {
                            line += mPrimaryOrientation.getEndAfterPadding();
                        } else {
                            line += mPrimaryOrientation.getStartAfterPadding();
                        }
                    }
                    mSpans[i].setLine(line);
                }
            } else {
                mPendingSavedState.invalidateSpanInfo();
                mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
            }
        }
        mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
        setReverseLayout(mPendingSavedState.mReverseLayout);
        resolveShouldLayoutReverse();

        if (mPendingSavedState.mAnchorPosition != NO_POSITION) {
            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
        } else {
            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
        }
        if (mPendingSavedState.mSpanLookupSize > 1) {
            mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
            mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
        }
    
booleanareAllEndsEqual()

        int end = mSpans[0].getEndLine(Span.INVALID_LINE);
        for (int i = 1; i < mSpanCount; i++) {
            if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
                return false;
            }
        }
        return true;
    
booleanareAllStartsEqual()

        int start = mSpans[0].getStartLine(Span.INVALID_LINE);
        for (int i = 1; i < mSpanCount; i++) {
            if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
                return false;
            }
        }
        return true;
    
public voidassertNotInLayoutOrScroll(java.lang.String message)

        if (mPendingSavedState == null) {
            super.assertNotInLayoutOrScroll(message);
        }
    
private voidattachViewToSpans(android.view.View view, android.support.v7.widget.StaggeredGridLayoutManager$LayoutParams lp, LayoutState layoutState)

        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
            if (lp.mFullSpan) {
                appendViewToAllSpans(view);
            } else {
                lp.mSpan.appendToSpan(view);
            }
        } else {
            if (lp.mFullSpan) {
                prependViewToAllSpans(view);
            } else {
                lp.mSpan.prependToSpan(view);
            }
        }
    
private intcalculateScrollDirectionForPosition(int position)

        if (getChildCount() == 0) {
            return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
        }
        final int firstChildPos = getFirstChildPosition();
        return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
    
public booleancanScrollHorizontally()

        return mOrientation == HORIZONTAL;
    
public booleancanScrollVertically()

        return mOrientation == VERTICAL;
    
private voidcheckForGaps()
Checks for gaps in the UI that may be caused by adapter changes.

When a full span item is laid out in reverse direction, it sets a flag which we check when scroll is stopped (or re-layout happens) and re-layout after first valid item.

        if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE) {
            return;
        }
        final int minPos, maxPos;
        if (mShouldReverseLayout) {
            minPos = getLastChildPosition();
            maxPos = getFirstChildPosition();
        } else {
            minPos = getFirstChildPosition();
            maxPos = getLastChildPosition();
        }
        if (minPos == 0) {
            View gapView = hasGapsToFix();
            if (gapView != null) {
                mLazySpanLookup.clear();
                requestSimpleAnimationsInNextLayout();
                requestLayout();
                return;
            }
        }
        if (!mLaidOutInvalidFullSpan) {
            return;
        }
        int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
        final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
        if (invalidFsi == null) {
            mLaidOutInvalidFullSpan = false;
            mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
            return;
        }
        final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
                .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
                        invalidGapDir * -1, true);
        if (validFsi == null) {
            mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
        } else {
            mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
        }
        requestSimpleAnimationsInNextLayout();
        requestLayout();
    
public booleancheckLayoutParams(RecyclerView.LayoutParams lp)

        return lp instanceof LayoutParams;
    
private booleancheckSpanForGap(android.support.v7.widget.StaggeredGridLayoutManager$Span span)

        if (mShouldReverseLayout) {
            if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
                return true;
            }
        } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
            return true;
        }
        return false;
    
public intcomputeHorizontalScrollExtent(RecyclerView.State state)

        return computeScrollExtent(state);
    
public intcomputeHorizontalScrollOffset(RecyclerView.State state)

        return computeScrollOffset(state);
    
public intcomputeHorizontalScrollRange(RecyclerView.State state)

        return computeScrollRange(state);
    
private intcomputeScrollExtent(RecyclerView.State state)

        if (getChildCount() == 0) {
            return 0;
        }
        ensureOrientationHelper();
        return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                this, mSmoothScrollbarEnabled);
    
private intcomputeScrollOffset(RecyclerView.State state)

        if (getChildCount() == 0) {
            return 0;
        }
        ensureOrientationHelper();
        return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
    
private intcomputeScrollRange(RecyclerView.State state)

        if (getChildCount() == 0) {
            return 0;
        }
        ensureOrientationHelper();
        return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                this, mSmoothScrollbarEnabled);
    
public intcomputeVerticalScrollExtent(RecyclerView.State state)

        return computeScrollExtent(state);
    
public intcomputeVerticalScrollOffset(RecyclerView.State state)

        return computeScrollOffset(state);
    
public intcomputeVerticalScrollRange(RecyclerView.State state)

        return computeScrollRange(state);
    
private LazySpanLookup.FullSpanItemcreateFullSpanItemFromEnd(int newItemTop)

        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
        fsi.mGapPerSpan = new int[mSpanCount];
        for (int i = 0; i < mSpanCount; i++) {
            fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
        }
        return fsi;
    
private LazySpanLookup.FullSpanItemcreateFullSpanItemFromStart(int newItemBottom)

        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
        fsi.mGapPerSpan = new int[mSpanCount];
        for (int i = 0; i < mSpanCount; i++) {
            fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
        }
        return fsi;
    
private voidensureOrientationHelper()

        if (mPrimaryOrientation == null) {
            mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
            mSecondaryOrientation = OrientationHelper
                    .createOrientationHelper(this, 1 - mOrientation);
            mLayoutState = new LayoutState();
        }
    
private intfill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state)

        mRemainingSpans.set(0, mSpanCount, true);
        // The target position we are trying to reach.
        final int targetLine;
        /*
        * The line until which we can recycle, as long as we add views.
        * Keep in mind, it is still the line in layout direction which means; to calculate the
        * actual recycle line, we should subtract/add the size in orientation.
        */
        final int recycleLine;
        // Line of the furthest row.
        if (layoutState.mLayoutDirection == LAYOUT_END) {
            // ignore padding for recycler
            recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
            targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();

        } else { // LAYOUT_START
            // ignore padding for recycler
            recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
            targetLine = recycleLine - mLayoutState.mExtra -
                    mPrimaryOrientation.getStartAfterPadding();
        }
        updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);

        // the default coordinate to add new view.
        final int defaultNewViewLine = mShouldReverseLayout
                ? mPrimaryOrientation.getEndAfterPadding()
                : mPrimaryOrientation.getStartAfterPadding();

        while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
            View view = layoutState.next(recycler);
            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
            final int position = lp.getViewLayoutPosition();
            final int spanIndex = mLazySpanLookup.getSpan(position);
            Span currentSpan;
            final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
            if (assignSpan) {
                currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
                mLazySpanLookup.setSpan(position, currentSpan);
                if (DEBUG) {
                    Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
                }
            } else {
                if (DEBUG) {
                    Log.d(TAG, "using " + spanIndex + " for pos " + position);
                }
                currentSpan = mSpans[spanIndex];
            }
            // assign span before measuring so that item decorators can get updated span index
            lp.mSpan = currentSpan;
            if (layoutState.mLayoutDirection == LAYOUT_END) {
                addView(view);
            } else {
                addView(view, 0);
            }
            measureChildWithDecorationsAndMargin(view, lp);

            final int start;
            final int end;
            if (layoutState.mLayoutDirection == LAYOUT_END) {
                start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
                        : currentSpan.getEndLine(defaultNewViewLine);
                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
                if (assignSpan && lp.mFullSpan) {
                    LazySpanLookup.FullSpanItem fullSpanItem;
                    fullSpanItem = createFullSpanItemFromEnd(start);
                    fullSpanItem.mGapDir = LAYOUT_START;
                    fullSpanItem.mPosition = position;
                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
                }
            } else {
                end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
                        : currentSpan.getStartLine(defaultNewViewLine);
                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
                if (assignSpan && lp.mFullSpan) {
                    LazySpanLookup.FullSpanItem fullSpanItem;
                    fullSpanItem = createFullSpanItemFromStart(end);
                    fullSpanItem.mGapDir = LAYOUT_END;
                    fullSpanItem.mPosition = position;
                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
                }
            }

            // check if this item may create gaps in the future
            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
                if (assignSpan) {
                    mLaidOutInvalidFullSpan = true;
                } else {
                    final boolean hasInvalidGap;
                    if (layoutState.mLayoutDirection == LAYOUT_END) {
                        hasInvalidGap = !areAllEndsEqual();
                    } else { // layoutState.mLayoutDirection == LAYOUT_START
                        hasInvalidGap = !areAllStartsEqual();
                    }
                    if (hasInvalidGap) {
                        final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
                                .getFullSpanItem(position);
                        if (fullSpanItem != null) {
                            fullSpanItem.mHasUnwantedGapAfter = true;
                        }
                        mLaidOutInvalidFullSpan = true;
                    }
                }

            }
            attachViewToSpans(view, lp, layoutState);
            final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
                    : currentSpan.mIndex * mSizePerSpan +
                            mSecondaryOrientation.getStartAfterPadding();
            final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
            if (mOrientation == VERTICAL) {
                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
            } else {
                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
            }

            if (lp.mFullSpan) {
                updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
            } else {
                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
            }
            recycle(recycler, mLayoutState, currentSpan, recycleLine);
        }
        if (DEBUG) {
            Log.d(TAG, "fill, " + getChildCount());
        }
        if (mLayoutState.mLayoutDirection == LAYOUT_START) {
            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
            return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart));
        } else {
            final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
            return Math.max(0, mLayoutState.mAvailable + (max - recycleLine));
        }
    
public int[]findFirstCompletelyVisibleItemPositions(int[] into)
Returns the adapter position of the first completely visible view for each span.

Note that, this value is not affected by layout orientation or item order traversal. ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, not in the layout.

If RecyclerView has item decorators, they will be considered in calculations as well.

StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those views are ignored in this method.

param
into An array to put the results into. If you don't provide any, LayoutManager will create a new one.
return
The adapter position of the first fully visible item in each span. If a span does not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
see
#findFirstVisibleItemPositions(int[])
see
#findLastCompletelyVisibleItemPositions(int[])

        if (into == null) {
            into = new int[mSpanCount];
        } else if (into.length < mSpanCount) {
            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
        }
        for (int i = 0; i < mSpanCount; i++) {
            into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
        }
        return into;
    
private intfindFirstReferenceChildPosition(int itemCount)
Finds the first View that can be used as an anchor View.

return
Position of the View or 0 if it cannot find any such View.

        final int limit = getChildCount();
        for (int i = 0; i < limit; i++) {
            final View view = getChildAt(i);
            final int position = getPosition(view);
            if (position >= 0 && position < itemCount) {
                return position;
            }
        }
        return 0;
    
android.view.ViewfindFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible)

        ensureOrientationHelper();
        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
        View partiallyVisible = null;
        for (int i = getChildCount() - 1; i >= 0; i--) {
            final View child = getChildAt(i);
            if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart) {
                if (!fullyVisible || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
                    return child;
                } else if (acceptPartiallyVisible && partiallyVisible == null) {
                    partiallyVisible = child;
                }
            }
        }
        return partiallyVisible;
    
android.view.ViewfindFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible)

        ensureOrientationHelper();
        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
        final int limit = getChildCount();
        View partiallyVisible = null;
        for (int i = 0; i < limit; i++) {
            final View child = getChildAt(i);
            if (mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
                if ((!fullyVisible
                        || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)) {
                    return child;
                } else if (acceptPartiallyVisible && partiallyVisible == null) {
                    partiallyVisible = child;
                }
            }
        }
        return partiallyVisible;
    
intfindFirstVisibleItemPositionInt()
Finds the first fully visible child to be used as an anchor child if span count changes when state is restored. If no children is fully visible, returns a partially visible child instead of returning null.

        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) :
                findFirstVisibleItemClosestToStart(true, true);
        return first == null ? NO_POSITION : getPosition(first);
    
public int[]findFirstVisibleItemPositions(int[] into)
Returns the adapter position of the first visible view for each span.

Note that, this value is not affected by layout orientation or item order traversal. ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, not in the layout.

If RecyclerView has item decorators, they will be considered in calculations as well.

StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those views are ignored in this method.

param
into An array to put the results into. If you don't provide any, LayoutManager will create a new one.
return
The adapter position of the first visible item in each span. If a span does not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
see
#findFirstCompletelyVisibleItemPositions(int[])
see
#findLastVisibleItemPositions(int[])

        if (into == null) {
            into = new int[mSpanCount];
        } else if (into.length < mSpanCount) {
            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
        }
        for (int i = 0; i < mSpanCount; i++) {
            into[i] = mSpans[i].findFirstVisibleItemPosition();
        }
        return into;
    
public int[]findLastCompletelyVisibleItemPositions(int[] into)
Returns the adapter position of the last completely visible view for each span.

Note that, this value is not affected by layout orientation or item order traversal. ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, not in the layout.

If RecyclerView has item decorators, they will be considered in calculations as well.

StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those views are ignored in this method.

param
into An array to put the results into. If you don't provide any, LayoutManager will create a new one.
return
The adapter position of the last fully visible item in each span. If a span does not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
see
#findFirstCompletelyVisibleItemPositions(int[])
see
#findLastVisibleItemPositions(int[])

        if (into == null) {
            into = new int[mSpanCount];
        } else if (into.length < mSpanCount) {
            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
        }
        for (int i = 0; i < mSpanCount; i++) {
            into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
        }
        return into;
    
private intfindLastReferenceChildPosition(int itemCount)
Finds the last View that can be used as an anchor View.

return
Position of the View or 0 if it cannot find any such View.

        for (int i = getChildCount() - 1; i >= 0; i--) {
            final View view = getChildAt(i);
            final int position = getPosition(view);
            if (position >= 0 && position < itemCount) {
                return position;
            }
        }
        return 0;
    
public int[]findLastVisibleItemPositions(int[] into)
Returns the adapter position of the last visible view for each span.

Note that, this value is not affected by layout orientation or item order traversal. ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, not in the layout.

If RecyclerView has item decorators, they will be considered in calculations as well.

StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those views are ignored in this method.

param
into An array to put the results into. If you don't provide any, LayoutManager will create a new one.
return
The adapter position of the last visible item in each span. If a span does not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
see
#findLastCompletelyVisibleItemPositions(int[])
see
#findFirstVisibleItemPositions(int[])

        if (into == null) {
            into = new int[mSpanCount];
        } else if (into.length < mSpanCount) {
            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
        }
        for (int i = 0; i < mSpanCount; i++) {
            into[i] = mSpans[i].findLastVisibleItemPosition();
        }
        return into;
    
private voidfixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state, boolean canOffsetChildren)

        final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
        int fixOffset;
        if (gap > 0) {
            fixOffset = -scrollBy(-gap, recycler, state);
        } else {
            return; // nothing to fix
        }
        gap -= fixOffset;
        if (canOffsetChildren && gap > 0) {
            mPrimaryOrientation.offsetChildren(gap);
        }
    
private voidfixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state, boolean canOffsetChildren)

        final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
        int fixOffset;
        if (gap > 0) {
            fixOffset = scrollBy(gap, recycler, state);
        } else {
            return; // nothing to fix
        }
        gap -= fixOffset;
        if (canOffsetChildren && gap > 0) {
            mPrimaryOrientation.offsetChildren(-gap);
        }
    
public RecyclerView.LayoutParamsgenerateDefaultLayoutParams()

        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    
public RecyclerView.LayoutParamsgenerateLayoutParams(android.content.Context c, android.util.AttributeSet attrs)

        return new LayoutParams(c, attrs);
    
public RecyclerView.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams lp)

        if (lp instanceof ViewGroup.MarginLayoutParams) {
            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
        } else {
            return new LayoutParams(lp);
        }
    
public intgetColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

        if (mOrientation == VERTICAL) {
            return mSpanCount;
        }
        return super.getColumnCountForAccessibility(recycler, state);
    
private intgetFirstChildPosition()

        final int childCount = getChildCount();
        return childCount == 0 ? 0 : getPosition(getChildAt(0));
    
public intgetGapStrategy()
Returns the current gap handling strategy for StaggeredGridLayoutManager.

Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps, StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.

By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.

return
Current gap handling strategy.
see
#setGapStrategy(int)
see
#GAP_HANDLING_NONE
see
#GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS

        return mGapStrategy;
    
private intgetLastChildPosition()

        final int childCount = getChildCount();
        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
    
private intgetMaxEnd(int def)

        int maxEnd = mSpans[0].getEndLine(def);
        for (int i = 1; i < mSpanCount; i++) {
            final int spanEnd = mSpans[i].getEndLine(def);
            if (spanEnd > maxEnd) {
                maxEnd = spanEnd;
            }
        }
        return maxEnd;
    
private intgetMaxStart(int def)

        int maxStart = mSpans[0].getStartLine(def);
        for (int i = 1; i < mSpanCount; i++) {
            final int spanStart = mSpans[i].getStartLine(def);
            if (spanStart > maxStart) {
                maxStart = spanStart;
            }
        }
        return maxStart;
    
private intgetMinEnd(int def)

        int minEnd = mSpans[0].getEndLine(def);
        for (int i = 1; i < mSpanCount; i++) {
            final int spanEnd = mSpans[i].getEndLine(def);
            if (spanEnd < minEnd) {
                minEnd = spanEnd;
            }
        }
        return minEnd;
    
private intgetMinStart(int def)

        int minStart = mSpans[0].getStartLine(def);
        for (int i = 1; i < mSpanCount; i++) {
            final int spanStart = mSpans[i].getStartLine(def);
            if (spanStart < minStart) {
                minStart = spanStart;
            }
        }
        return minStart;
    
private android.support.v7.widget.StaggeredGridLayoutManager$SpangetNextSpan(LayoutState layoutState)
Finds the span for the next view.

        final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
        final int startIndex, endIndex, diff;
        if (preferLastSpan) {
            startIndex = mSpanCount - 1;
            endIndex = -1;
            diff = -1;
        } else {
            startIndex = 0;
            endIndex = mSpanCount;
            diff = 1;
        }
        if (layoutState.mLayoutDirection == LAYOUT_END) {
            Span min = null;
            int minLine = Integer.MAX_VALUE;
            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
            for (int i = startIndex; i != endIndex; i += diff) {
                final Span other = mSpans[i];
                int otherLine = other.getEndLine(defaultLine);
                if (otherLine < minLine) {
                    min = other;
                    minLine = otherLine;
                }
            }
            return min;
        } else {
            Span max = null;
            int maxLine = Integer.MIN_VALUE;
            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
            for (int i = startIndex; i != endIndex; i += diff) {
                final Span other = mSpans[i];
                int otherLine = other.getStartLine(defaultLine);
                if (otherLine > maxLine) {
                    max = other;
                    maxLine = otherLine;
                }
            }
            return max;
        }
    
public intgetOrientation()

        return mOrientation;
    
public booleangetReverseLayout()
Returns whether views are laid out in reverse order or not.

Not that this value is not affected by RecyclerView's layout direction.

return
True if layout is reversed, false otherwise
see
#setReverseLayout(boolean)

        return mReverseLayout;
    
public intgetRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

        if (mOrientation == HORIZONTAL) {
            return mSpanCount;
        }
        return super.getRowCountForAccessibility(recycler, state);
    
public intgetSpanCount()
Returns the number of spans laid out by StaggeredGridLayoutManager.

return
Number of spans in the layout

        return mSpanCount;
    
private intgetSpecForDimension(int dim, int defaultSpec)

        if (dim < 0) {
            return defaultSpec;
        } else {
            return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
        }
    
private voidhandleUpdate(int positionStart, int itemCountOrToPosition, int cmd)
Checks whether it should invalidate span assignments in response to an adapter change.

        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
        final int affectedRangeEnd;// exclusive
        final int affectedRangeStart;// inclusive

        if (cmd == AdapterHelper.UpdateOp.MOVE) {
            if (positionStart < itemCountOrToPosition) {
                affectedRangeEnd = itemCountOrToPosition + 1;
                affectedRangeStart = positionStart;
            } else {
                affectedRangeEnd = positionStart + 1;
                affectedRangeStart = itemCountOrToPosition;
            }
        } else {
            affectedRangeStart = positionStart;
            affectedRangeEnd = positionStart + itemCountOrToPosition;
        }

        mLazySpanLookup.invalidateAfter(affectedRangeStart);
        switch (cmd) {
            case AdapterHelper.UpdateOp.ADD:
                mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
                break;
            case AdapterHelper.UpdateOp.REMOVE:
                mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
                break;
            case AdapterHelper.UpdateOp.MOVE:
                // TODO optimize
                mLazySpanLookup.offsetForRemoval(positionStart, 1);
                mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
                break;
        }

        if (affectedRangeEnd <= minPosition) {
            return;
        }

        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
        if (affectedRangeStart <= maxPosition) {
            requestLayout();
        }
    
android.view.ViewhasGapsToFix()
Checks for gaps if we've reached to the top of the list.

Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.

        int startChildIndex = 0;
        int endChildIndex = getChildCount() - 1;
        BitSet mSpansToCheck = new BitSet(mSpanCount);
        mSpansToCheck.set(0, mSpanCount, true);

        final int firstChildIndex, childLimit;
        final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;

        if (mShouldReverseLayout) {
            firstChildIndex = endChildIndex - 1;
            childLimit = startChildIndex - 1;
        } else {
            firstChildIndex = startChildIndex;
            childLimit = endChildIndex;
        }
        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (mSpansToCheck.get(lp.mSpan.mIndex)) {
                if (checkSpanForGap(lp.mSpan)) {
                    return child;
                }
                mSpansToCheck.clear(lp.mSpan.mIndex);
            }
            if (lp.mFullSpan) {
                continue; // quick reject
            }

            if (i + nextChildDiff != childLimit) {
                View nextChild = getChildAt(i + nextChildDiff);
                boolean compareSpans = false;
                if (mShouldReverseLayout) {
                    // ensure child's end is below nextChild's end
                    int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
                    int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
                    if (myEnd < nextEnd) {
                        return child;//i should have a better position
                    } else if (myEnd == nextEnd) {
                        compareSpans = true;
                    }
                } else {
                    int myStart = mPrimaryOrientation.getDecoratedStart(child);
                    int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
                    if (myStart > nextStart) {
                        return child;//i should have a better position
                    } else if (myStart == nextStart) {
                        compareSpans = true;
                    }
                }
                if (compareSpans) {
                    // equal, check span indices.
                    LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
                    if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
                        return child;
                    }
                }
            }
        }
        // everything looks good
        return null;
    
public voidinvalidateSpanAssignments()
For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.

If you need to cancel current assignments, you can call this method which will clear all assignments and request a new layout.

        mLazySpanLookup.clear();
        requestLayout();
    
booleanisLayoutRTL()

        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
    
private voidlayoutDecoratedWithMargins(android.view.View child, int left, int top, int right, int bottom)

        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (DEBUG) {
            Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:"
                    + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
                    + ". l:" + left + ",t:" + top
                    + ", r:" + right + ", b:" + bottom);
        }
        layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
                , bottom - lp.bottomMargin);
    
private voidmeasureChildWithDecorationsAndMargin(android.view.View child, android.support.v7.widget.StaggeredGridLayoutManager$LayoutParams lp)

        if (lp.mFullSpan) {
            if (mOrientation == VERTICAL) {
                measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
                        getSpecForDimension(lp.height, mHeightSpec));
            } else {
                measureChildWithDecorationsAndMargin(child,
                        getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
            }
        } else {
            if (mOrientation == VERTICAL) {
                measureChildWithDecorationsAndMargin(child, mWidthSpec,
                        getSpecForDimension(lp.height, mHeightSpec));
            } else {
                measureChildWithDecorationsAndMargin(child,
                        getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
            }
        }
    
private voidmeasureChildWithDecorationsAndMargin(android.view.View child, int widthSpec, int heightSpec)

        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + insets.left,
                lp.rightMargin + insets.right);
        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + insets.top,
                lp.bottomMargin + insets.bottom);
        child.measure(widthSpec, heightSpec);
    
public voidoffsetChildrenHorizontal(int dx)

        super.offsetChildrenHorizontal(dx);
        for (int i = 0; i < mSpanCount; i++) {
            mSpans[i].onOffset(dx);
        }
    
public voidoffsetChildrenVertical(int dy)

        super.offsetChildrenVertical(dy);
        for (int i = 0; i < mSpanCount; i++) {
            mSpans[i].onOffset(dy);
        }
    
public voidonDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler)

        for (int i = 0; i < mSpanCount; i++) {
            mSpans[i].clear();
        }
    
public voidonInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        super.onInitializeAccessibilityEvent(event);
        if (getChildCount() > 0) {
            final AccessibilityRecordCompat record = AccessibilityEventCompat
                    .asRecord(event);
            final View start = findFirstVisibleItemClosestToStart(false, true);
            final View end = findFirstVisibleItemClosestToEnd(false, true);
            if (start == null || end == null) {
                return;
            }
            final int startPos = getPosition(start);
            final int endPos = getPosition(end);
            if (startPos < endPos) {
                record.setFromIndex(startPos);
                record.setToIndex(endPos);
            } else {
                record.setFromIndex(endPos);
                record.setToIndex(startPos);
            }
        }
    
public voidonInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, android.view.View host, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat info)

        ViewGroup.LayoutParams lp = host.getLayoutParams();
        if (!(lp instanceof LayoutParams)) {
            super.onInitializeAccessibilityNodeInfoForItem(host, info);
            return;
        }
        LayoutParams sglp = (LayoutParams) lp;
        if (mOrientation == HORIZONTAL) {
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
                    -1, -1,
                    sglp.mFullSpan, false));
        } else { // VERTICAL
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                    -1, -1,
                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
                    sglp.mFullSpan, false));
        }
    
public voidonItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)

        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
    
public voidonItemsChanged(RecyclerView recyclerView)

        mLazySpanLookup.clear();
        requestLayout();
    
public voidonItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)

        handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
    
public voidonItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)

        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
    
public voidonItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount)

        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
    
public voidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)

        ensureOrientationHelper();

        final AnchorInfo anchorInfo = mAnchorInfo;
        anchorInfo.reset();

        if (mPendingSavedState != null) {
            applyPendingSavedState(anchorInfo);
        } else {
            resolveShouldLayoutReverse();
            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
        }

        updateAnchorInfoForLayout(state, anchorInfo);

        if (mPendingSavedState == null) {
            if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
                    isLayoutRTL() != mLastLayoutRTL) {
                mLazySpanLookup.clear();
                anchorInfo.mInvalidateOffsets = true;
            }
        }

        if (getChildCount() > 0 && (mPendingSavedState == null ||
                mPendingSavedState.mSpanOffsetsSize < 1)) {
            if (anchorInfo.mInvalidateOffsets) {
                for (int i = 0; i < mSpanCount; i++) {
                    // Scroll to position is set, clear.
                    mSpans[i].clear();
                    if (anchorInfo.mOffset != INVALID_OFFSET) {
                        mSpans[i].setLine(anchorInfo.mOffset);
                    }
                }
            } else {
                for (int i = 0; i < mSpanCount; i++) {
                    mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset);
                }
            }
        }
        detachAndScrapAttachedViews(recycler);
        mLaidOutInvalidFullSpan = false;
        updateMeasureSpecs();
        if (anchorInfo.mLayoutFromEnd) {
            // Layout start.
            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
            fill(recycler, mLayoutState, state);
            // Layout end.
            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state);
        } else {
            // Layout end.
            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
            fill(recycler, mLayoutState, state);
            // Layout start.
            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state);
        }

        if (getChildCount() > 0) {
            if (mShouldReverseLayout) {
                fixEndGap(recycler, state, true);
                fixStartGap(recycler, state, false);
            } else {
                fixStartGap(recycler, state, true);
                fixEndGap(recycler, state, false);
            }
        }

        if (!state.isPreLayout()) {
            if (getChildCount() > 0 && mPendingScrollPosition != NO_POSITION &&
                    mLaidOutInvalidFullSpan) {
                ViewCompat.postOnAnimation(getChildAt(0), checkForGapsRunnable);
            }
            mPendingScrollPosition = NO_POSITION;
            mPendingScrollPositionOffset = INVALID_OFFSET;
        }
        mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
        mLastLayoutRTL = isLayoutRTL();
        mPendingSavedState = null; // we don't need this anymore
    
public voidonRestoreInstanceState(android.os.Parcelable state)

        if (state instanceof SavedState) {
            mPendingSavedState = (SavedState) state;
            requestLayout();
        } else if (DEBUG) {
            Log.d(TAG, "invalid saved state class");
        }
    
public android.os.ParcelableonSaveInstanceState()

        if (mPendingSavedState != null) {
            return new SavedState(mPendingSavedState);
        }
        SavedState state = new SavedState();
        state.mReverseLayout = mReverseLayout;
        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
        state.mLastLayoutRTL = mLastLayoutRTL;

        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
            state.mSpanLookup = mLazySpanLookup.mData;
            state.mSpanLookupSize = state.mSpanLookup.length;
            state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
        } else {
            state.mSpanLookupSize = 0;
        }

        if (getChildCount() > 0) {
            ensureOrientationHelper();
            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
                    : getFirstChildPosition();
            state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
            state.mSpanOffsetsSize = mSpanCount;
            state.mSpanOffsets = new int[mSpanCount];
            for (int i = 0; i < mSpanCount; i++) {
                int line;
                if (mLastLayoutFromEnd) {
                    line = mSpans[i].getEndLine(Span.INVALID_LINE);
                    if (line != Span.INVALID_LINE) {
                        line -= mPrimaryOrientation.getEndAfterPadding();
                    }
                } else {
                    line = mSpans[i].getStartLine(Span.INVALID_LINE);
                    if (line != Span.INVALID_LINE) {
                        line -= mPrimaryOrientation.getStartAfterPadding();
                    }
                }
                state.mSpanOffsets[i] = line;
            }
        } else {
            state.mAnchorPosition = NO_POSITION;
            state.mVisibleAnchorPosition = NO_POSITION;
            state.mSpanOffsetsSize = 0;
        }
        if (DEBUG) {
            Log.d(TAG, "saved state:\n" + state);
        }
        return state;
    
public voidonScrollStateChanged(int state)

        if (state == RecyclerView.SCROLL_STATE_IDLE) {
            checkForGaps();
        }
    
private booleanpreferLastSpan(int layoutDir)

return
True if last span is the first one we want to fill

        if (mOrientation == HORIZONTAL) {
            return (layoutDir == LAYOUT_START) != mShouldReverseLayout;
        }
        return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
    
private voidprependViewToAllSpans(android.view.View view)

        // traverse in reverse so that we end up assigning full span items to 0
        for (int i = mSpanCount - 1; i >= 0; i--) {
            mSpans[i].prependToSpan(view);
        }
    
private voidrecycle(RecyclerView.Recycler recycler, LayoutState layoutState, android.support.v7.widget.StaggeredGridLayoutManager$Span updatedSpan, int recycleLine)

        if (layoutState.mLayoutDirection == LAYOUT_START) {
            // calculate recycle line
            int maxStart = getMaxStart(updatedSpan.getStartLine());
            recycleFromEnd(recycler, Math.max(recycleLine, maxStart) +
                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
        } else {
            // calculate recycle line
            int minEnd = getMinEnd(updatedSpan.getEndLine());
            recycleFromStart(recycler, Math.min(recycleLine, minEnd) -
                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
        }
    
private voidrecycleFromEnd(RecyclerView.Recycler recycler, int line)

        final int childCount = getChildCount();
        int i;
        for (i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mPrimaryOrientation.getDecoratedStart(child) > line) {
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp.mFullSpan) {
                    for (int j = 0; j < mSpanCount; j++) {
                        mSpans[j].popEnd();
                    }
                } else {
                    lp.mSpan.popEnd();
                }
                removeAndRecycleView(child, recycler);
            } else {
                return;// done
            }
        }
    
private voidrecycleFromStart(RecyclerView.Recycler recycler, int line)

        if (DEBUG) {
            Log.d(TAG, "recycling from start for line " + line);
        }
        while (getChildCount() > 0) {
            View child = getChildAt(0);
            if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp.mFullSpan) {
                    for (int j = 0; j < mSpanCount; j++) {
                        mSpans[j].popStart();
                    }
                } else {
                    lp.mSpan.popStart();
                }
                removeAndRecycleView(child, recycler);
            } else {
                return;// done
            }
        }
    
private voidresolveShouldLayoutReverse()
Calculates the views' layout order. (e.g. from end to start or start to end) RTL layout support is applied automatically. So if layout is RTL and {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.

        // A == B is the same result, but we rather keep it readable
        if (mOrientation == VERTICAL || !isLayoutRTL()) {
            mShouldReverseLayout = mReverseLayout;
        } else {
            mShouldReverseLayout = !mReverseLayout;
        }
    
intscrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state)

        ensureOrientationHelper();
        final int referenceChildPosition;
        if (dt > 0) { // layout towards end
            mLayoutState.mLayoutDirection = LAYOUT_END;
            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
                    : ITEM_DIRECTION_TAIL;
            referenceChildPosition = getLastChildPosition();
        } else {
            mLayoutState.mLayoutDirection = LAYOUT_START;
            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
                    : ITEM_DIRECTION_HEAD;
            referenceChildPosition = getFirstChildPosition();
        }
        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
        final int absDt = Math.abs(dt);
        mLayoutState.mAvailable = absDt;
        mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
        int consumed = fill(recycler, mLayoutState, state);
        final int totalScroll;
        if (absDt < consumed) {
            totalScroll = dt;
        } else if (dt < 0) {
            totalScroll = -consumed;
        } else { // dt > 0
            totalScroll = consumed;
        }
        if (DEBUG) {
            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
        }

        mPrimaryOrientation.offsetChildren(-totalScroll);
        // always reset this if we scroll for a proper save instance state
        mLastLayoutFromEnd = mShouldReverseLayout;
        return totalScroll;
    
public intscrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)

        return scrollBy(dx, recycler, state);
    
public voidscrollToPosition(int position)

        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
            mPendingSavedState.invalidateAnchorPositionInfo();
        }
        mPendingScrollPosition = position;
        mPendingScrollPositionOffset = INVALID_OFFSET;
        requestLayout();
    
public voidscrollToPositionWithOffset(int position, int offset)
Scroll to the specified adapter position with the given offset from layout start.

Note that scroll position change will not be reflected until the next layout call.

If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.

param
position Index (starting at 0) of the reference item.
param
offset The distance (in pixels) between the start edge of the item view and start edge of the RecyclerView.
see
#setReverseLayout(boolean)
see
#scrollToPosition(int)

        if (mPendingSavedState != null) {
            mPendingSavedState.invalidateAnchorPositionInfo();
        }
        mPendingScrollPosition = position;
        mPendingScrollPositionOffset = offset;
        requestLayout();
    
public intscrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)

        return scrollBy(dy, recycler, state);
    
public voidsetGapStrategy(int gapStrategy)
Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter is different than the current strategy, calling this method will trigger a layout request.

param
gapStrategy The new gap handling strategy. Should be {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link #GAP_HANDLING_NONE}.
see
#getGapStrategy()

        assertNotInLayoutOrScroll(null);
        if (gapStrategy == mGapStrategy) {
            return;
        }
        if (gapStrategy != GAP_HANDLING_NONE &&
                gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
                    + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
        }
        mGapStrategy = gapStrategy;
        requestLayout();
    
public voidsetOrientation(int orientation)
Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep scroll position if this method is called after views are laid out.

param
orientation {@link #HORIZONTAL} or {@link #VERTICAL}

        if (orientation != HORIZONTAL && orientation != VERTICAL) {
            throw new IllegalArgumentException("invalid orientation.");
        }
        assertNotInLayoutOrScroll(null);
        if (orientation == mOrientation) {
            return;
        }
        mOrientation = orientation;
        if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
            // swap
            OrientationHelper tmp = mPrimaryOrientation;
            mPrimaryOrientation = mSecondaryOrientation;
            mSecondaryOrientation = tmp;
        }
        requestLayout();
    
public voidsetReverseLayout(boolean reverseLayout)
Sets whether LayoutManager should start laying out items from the end of the UI. The order items are traversed is not affected by this call.

For vertical layout, if it is set to true, first item will be at the bottom of the list.

For horizontal layouts, it depends on the layout direction. When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if {@link RecyclerView}} is RTL, it will layout from LTR.

param
reverseLayout Whether layout should be in reverse or not

        assertNotInLayoutOrScroll(null);
        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
            mPendingSavedState.mReverseLayout = reverseLayout;
        }
        mReverseLayout = reverseLayout;
        requestLayout();
    
public voidsetSpanCount(int spanCount)
Sets the number of spans for the layout. This will invalidate all of the span assignments for Views.

Calling this method will automatically result in a new layout request unless the spanCount parameter is equal to current span count.

param
spanCount Number of spans to layout

        assertNotInLayoutOrScroll(null);
        if (spanCount != mSpanCount) {
            invalidateSpanAssignments();
            mSpanCount = spanCount;
            mRemainingSpans = new BitSet(mSpanCount);
            mSpans = new Span[mSpanCount];
            for (int i = 0; i < mSpanCount; i++) {
                mSpans[i] = new Span(i);
            }
            requestLayout();
        }
    
public voidsmoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)

        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
            @Override
            public PointF computeScrollVectorForPosition(int targetPosition) {
                final int direction = calculateScrollDirectionForPosition(targetPosition);
                if (direction == 0) {
                    return null;
                }
                if (mOrientation == HORIZONTAL) {
                    return new PointF(direction, 0);
                } else {
                    return new PointF(0, direction);
                }
            }
        };
        scroller.setTargetPosition(position);
        startSmoothScroll(scroller);
    
public booleansupportsPredictiveItemAnimations()

        return mPendingSavedState == null;
    
private voidupdateAllRemainingSpans(int layoutDir, int targetLine)

        for (int i = 0; i < mSpanCount; i++) {
            if (mSpans[i].mViews.isEmpty()) {
                continue;
            }
            updateRemainingSpans(mSpans[i], layoutDir, targetLine);
        }
    
private booleanupdateAnchorFromChildren(RecyclerView.State state, android.support.v7.widget.StaggeredGridLayoutManager$AnchorInfo anchorInfo)

        // We don't recycle views out of adapter order. This way, we can rely on the first or
        // last child as the anchor position.
        // Layout direction may change but we should select the child depending on the latest
        // layout direction. Otherwise, we'll choose the wrong child.
        anchorInfo.mPosition = mLastLayoutFromEnd
                ? findLastReferenceChildPosition(state.getItemCount())
                : findFirstReferenceChildPosition(state.getItemCount());
        anchorInfo.mOffset = INVALID_OFFSET;
        return true;
    
booleanupdateAnchorFromPendingData(RecyclerView.State state, android.support.v7.widget.StaggeredGridLayoutManager$AnchorInfo anchorInfo)

        // Validate scroll position if exists.
        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
            return false;
        }
        // Validate it.
        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
            mPendingScrollPosition = NO_POSITION;
            mPendingScrollPositionOffset = INVALID_OFFSET;
            return false;
        }

        if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION
                || mPendingSavedState.mSpanOffsetsSize < 1) {
            // If item is visible, make it fully visible.
            final View child = findViewByPosition(mPendingScrollPosition);
            if (child != null) {
                // Use regular anchor position, offset according to pending offset and target
                // child
                anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
                        : getFirstChildPosition();

                if (mPendingScrollPositionOffset != INVALID_OFFSET) {
                    if (anchorInfo.mLayoutFromEnd) {
                        final int target = mPrimaryOrientation.getEndAfterPadding() -
                                mPendingScrollPositionOffset;
                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
                    } else {
                        final int target = mPrimaryOrientation.getStartAfterPadding() +
                                mPendingScrollPositionOffset;
                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
                    }
                    return true;
                }

                // no offset provided. Decide according to the child location
                final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
                if (childSize > mPrimaryOrientation.getTotalSpace()) {
                    // Item does not fit. Fix depending on layout direction.
                    anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
                            ? mPrimaryOrientation.getEndAfterPadding()
                            : mPrimaryOrientation.getStartAfterPadding();
                    return true;
                }

                final int startGap = mPrimaryOrientation.getDecoratedStart(child)
                        - mPrimaryOrientation.getStartAfterPadding();
                if (startGap < 0) {
                    anchorInfo.mOffset = -startGap;
                    return true;
                }
                final int endGap = mPrimaryOrientation.getEndAfterPadding() -
                        mPrimaryOrientation.getDecoratedEnd(child);
                if (endGap < 0) {
                    anchorInfo.mOffset = endGap;
                    return true;
                }
                // child already visible. just layout as usual
                anchorInfo.mOffset = INVALID_OFFSET;
            } else {
                // Child is not visible. Set anchor coordinate depending on in which direction
                // child will be visible.
                anchorInfo.mPosition = mPendingScrollPosition;
                if (mPendingScrollPositionOffset == INVALID_OFFSET) {
                    final int position = calculateScrollDirectionForPosition(
                            anchorInfo.mPosition);
                    anchorInfo.mLayoutFromEnd = position == LAYOUT_END;
                    anchorInfo.assignCoordinateFromPadding();
                } else {
                    anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
                }
                anchorInfo.mInvalidateOffsets = true;
            }
        } else {
            anchorInfo.mOffset = INVALID_OFFSET;
            anchorInfo.mPosition = mPendingScrollPosition;
        }
        return true;
    
voidupdateAnchorInfoForLayout(RecyclerView.State state, android.support.v7.widget.StaggeredGridLayoutManager$AnchorInfo anchorInfo)

        if (updateAnchorFromPendingData(state, anchorInfo)) {
            return;
        }
        if (updateAnchorFromChildren(state, anchorInfo)) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "Deciding anchor info from fresh state");
        }
        anchorInfo.assignCoordinateFromPadding();
        anchorInfo.mPosition = 0;
    
private voidupdateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state)

        mLayoutState.mAvailable = 0;
        mLayoutState.mCurrentPosition = anchorPosition;
        if (isSmoothScrolling()) {
            final int targetPos = state.getTargetScrollPosition();
            if (mShouldReverseLayout == targetPos > anchorPosition) {
                mLayoutState.mExtra = 0;
            } else {
                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
            }
        } else {
            mLayoutState.mExtra = 0;
        }
        mLayoutState.mLayoutDirection = LAYOUT_END;
        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
                : ITEM_DIRECTION_TAIL;
    
private voidupdateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state)

        mLayoutState.mAvailable = 0;
        mLayoutState.mCurrentPosition = anchorPosition;
        if (isSmoothScrolling()) {
            final int targetPos = state.getTargetScrollPosition();
            if (mShouldReverseLayout == targetPos < anchorPosition) {
                mLayoutState.mExtra = 0;
            } else {
                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
            }
        } else {
            mLayoutState.mExtra = 0;
        }
        mLayoutState.mLayoutDirection = LAYOUT_START;
        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
                : ITEM_DIRECTION_HEAD;
    
voidupdateMeasureSpecs()

        mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
        mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
                mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
        if (mOrientation == VERTICAL) {
            mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
            mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        } else {
            mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
            mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        }
    
private voidupdateRemainingSpans(android.support.v7.widget.StaggeredGridLayoutManager$Span span, int layoutDir, int targetLine)

        final int deletedSize = span.getDeletedSize();
        if (layoutDir == LAYOUT_START) {
            final int line = span.getStartLine();
            if (line + deletedSize < targetLine) {
                mRemainingSpans.set(span.mIndex, false);
            }
        } else {
            final int line = span.getEndLine();
            if (line - deletedSize > targetLine) {
                mRemainingSpans.set(span.mIndex, false);
            }
        }
    
private intupdateSpecWithExtra(int spec, int startInset, int endInset)

        if (startInset == 0 && endInset == 0) {
            return spec;
        }
        final int mode = View.MeasureSpec.getMode(spec);
        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
            return View.MeasureSpec.makeMeasureSpec(
                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
        }
        return spec;