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

GridLayoutManager

public class GridLayoutManager extends LinearLayoutManager
A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.

By default, each item occupies 1 span. You can change it by providing a custom {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.

Fields Summary
private static final boolean
DEBUG
private static final String
TAG
public static final int
DEFAULT_SPAN_COUNT
static final int
MAIN_DIR_SPEC
The measure spec for the scroll direction.
int
mSpanCount
int
mSizePerSpan
The size of each span
android.view.View[]
mSet
Temporary array to keep views in layoutChunk method
final android.util.SparseIntArray
mPreLayoutSpanSizeCache
final android.util.SparseIntArray
mPreLayoutSpanIndexCache
SpanSizeLookup
mSpanSizeLookup
final android.graphics.Rect
mDecorInsets
Constructors Summary
public GridLayoutManager(android.content.Context context, int spanCount)
Creates a vertical GridLayoutManager

param
context Current context, will be used to access resources.
param
spanCount The number of columns in the grid


                                
         
        super(context);
        setSpanCount(spanCount);
    
public GridLayoutManager(android.content.Context context, int spanCount, int orientation, boolean reverseLayout)

param
context Current context, will be used to access resources.
param
spanCount The number of columns or rows in the grid
param
orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
param
reverseLayout When set to true, layouts from end to start.

        super(context, orientation, reverseLayout);
        setSpanCount(spanCount);
    
Methods Summary
private voidassignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, int consumedSpanCount, boolean layingOutInPrimaryDirection)

        int span, spanDiff, start, end, diff;
        // make sure we traverse from min position to max position
        if (layingOutInPrimaryDirection) {
            start = 0;
            end = count;
            diff = 1;
        } else {
            start = count - 1;
            end = -1;
            diff = -1;
        }
        if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
            span = consumedSpanCount - 1;
            spanDiff = -1;
        } else {
            span = 0;
            spanDiff = 1;
        }
        for (int i = start; i != end; i += diff) {
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
            if (spanDiff == -1 && params.mSpanSize > 1) {
                params.mSpanIndex = span - (params.mSpanSize - 1);
            } else {
                params.mSpanIndex = span;
            }
            span += spanDiff * params.mSpanSize;
        }
    
private voidcachePreLayoutSpanMapping()

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
            final int viewPosition = lp.getViewLayoutPosition();
            mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
            mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
        }
    
public booleancheckLayoutParams(RecyclerView.LayoutParams lp)

        return lp instanceof LayoutParams;
    
private voidclearPreLayoutSpanMappingCache()

        mPreLayoutSpanSizeCache.clear();
        mPreLayoutSpanIndexCache.clear();
    
private voidensureAnchorIsInFirstSpan(AnchorInfo anchorInfo)

        int span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
        while (span > 0 && anchorInfo.mPosition > 0) {
            anchorInfo.mPosition--;
            span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
        }
    
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;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }
        return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
    
private intgetMainDirSpec(int dim)

        if (dim < 0) {
            return MAIN_DIR_SPEC;
        } else {
            return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
        }
    
public intgetRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

        if (mOrientation == HORIZONTAL) {
            return mSpanCount;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }
        return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
    
public intgetSpanCount()
Returns the number of spans laid out by this grid.

return
The number of spans
see
#setSpanCount(int)

        return mSpanCount;
    
private intgetSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int viewPosition)

        if (!state.isPreLayout()) {
            return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
        }
        final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
        if (adapterPosition == -1) {
            if (DEBUG) {
                throw new RuntimeException("Cannot find span group index for position "
                        + viewPosition);
            }
            Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
            return 0;
        }
        return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
    
private intgetSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)

        if (!state.isPreLayout()) {
            return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
        }
        final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
        if (cached != -1) {
            return cached;
        }
        final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
        if (adapterPosition == -1) {
            if (DEBUG) {
                throw new RuntimeException("Cannot find span index for pre layout position. It is"
                        + " not cached, not in the adapter. Pos:" + pos);
            }
            Log.w(TAG, "Cannot find span size for pre layout position. It is"
                    + " not cached, not in the adapter. Pos:" + pos);
            return 0;
        }
        return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
    
private intgetSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)

        if (!state.isPreLayout()) {
            return mSpanSizeLookup.getSpanSize(pos);
        }
        final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
        if (cached != -1) {
            return cached;
        }
        final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
        if (adapterPosition == -1) {
            if (DEBUG) {
                throw new RuntimeException("Cannot find span size for pre layout position. It is"
                        + " not cached, not in the adapter. Pos:" + pos);
            }
            Log.w(TAG, "Cannot find span size for pre layout position. It is"
                    + " not cached, not in the adapter. Pos:" + pos);
            return 1;
        }
        return mSpanSizeLookup.getSpanSize(adapterPosition);
    
public android.support.v7.widget.GridLayoutManager$SpanSizeLookupgetSpanSizeLookup()
Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.

return
The current {@link SpanSizeLookup} used by the GridLayoutManager.

        return mSpanSizeLookup;
    
voidlayoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result)

        final boolean layingOutInPrimaryDirection =
                layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
        int count = 0;
        int consumedSpanCount = 0;
        int remainingSpan = mSpanCount;
        if (!layingOutInPrimaryDirection) {
            int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
            int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
            remainingSpan = itemSpanIndex + itemSpanSize;
        }
        while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
            int pos = layoutState.mCurrentPosition;
            final int spanSize = getSpanSize(recycler, state, pos);
            if (spanSize > mSpanCount) {
                throw new IllegalArgumentException("Item at position " + pos + " requires " +
                        spanSize + " spans but GridLayoutManager has only " + mSpanCount
                        + " spans.");
            }
            remainingSpan -= spanSize;
            if (remainingSpan < 0) {
                break; // item did not fit into this row or column
            }
            View view = layoutState.next(recycler);
            if (view == null) {
                break;
            }
            consumedSpanCount += spanSize;
            mSet[count] = view;
            count++;
        }

        if (count == 0) {
            result.mFinished = true;
            return;
        }

        int maxSize = 0;

        // we should assign spans before item decor offsets are calculated
        assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
        for (int i = 0; i < count; i++) {
            View view = mSet[i];
            if (layoutState.mScrapList == null) {
                if (layingOutInPrimaryDirection) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                if (layingOutInPrimaryDirection) {
                    addDisappearingView(view);
                } else {
                    addDisappearingView(view, 0);
                }
            }

            int spanSize = getSpanSize(recycler, state, getPosition(view));
            final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
                    View.MeasureSpec.EXACTLY);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (mOrientation == VERTICAL) {
                measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height));
            } else {
                measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec);
            }
            final int size = mOrientationHelper.getDecoratedMeasurement(view);
            if (size > maxSize) {
                maxSize = size;
            }
        }

        // views that did not measure the maxSize has to be re-measured
        final int maxMeasureSpec = getMainDirSpec(maxSize);
        for (int i = 0; i < count; i ++) {
            final View view = mSet[i];
            if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
                int spanSize = getSpanSize(recycler, state, getPosition(view));
                final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
                        View.MeasureSpec.EXACTLY);
                if (mOrientation == VERTICAL) {
                    measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec);
                } else {
                    measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec);
                }
            }
        }

        result.mConsumed = maxSize;

        int left = 0, right = 0, top = 0, bottom = 0;
        if (mOrientation == VERTICAL) {
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = bottom - maxSize;
            } else {
                top = layoutState.mOffset;
                bottom = top + maxSize;
            }
        } else {
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = right - maxSize;
            } else {
                left = layoutState.mOffset;
                right = left + maxSize;
            }
        }
        for (int i = 0; i < count; i++) {
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            if (mOrientation == VERTICAL) {
                left = getPaddingLeft() + mSizePerSpan * params.mSpanIndex;
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                top = getPaddingTop() + mSizePerSpan * params.mSpanIndex;
                bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            // We calculate everything with View's bounding box (which includes decor and margins)
            // To calculate correct layout position, we subtract margins.
            layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                    right - params.rightMargin, bottom - params.bottomMargin);
            if (DEBUG) {
                Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                        + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                        + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
                        + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
            }
            // Consume the available space if the view is not removed OR changed
            if (params.isItemRemoved() || params.isItemChanged()) {
                result.mIgnoreConsumed = true;
            }
            result.mFocusable |= view.isFocusable();
        }
        Arrays.fill(mSet, null);
    
private voidmeasureChildWithDecorationsAndMargin(android.view.View child, int widthSpec, int heightSpec)

        calculateItemDecorationsForChild(child, mDecorInsets);
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
                lp.rightMargin + mDecorInsets.right);
        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
                lp.bottomMargin + mDecorInsets.bottom);
        child.measure(widthSpec, heightSpec);
    
voidonAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo)

        super.onAnchorReady(state, anchorInfo);
        updateMeasurements();
        if (state.getItemCount() > 0 && !state.isPreLayout()) {
            ensureAnchorIsInFirstSpan(anchorInfo);
        }
        if (mSet == null || mSet.length != mSpanCount) {
            mSet = new View[mSpanCount];
        }
    
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 glp = (LayoutParams) lp;
        int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
        if (mOrientation == HORIZONTAL) {
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                    glp.getSpanIndex(), glp.getSpanSize(),
                    spanGroupIndex, 1,
                    mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
        } else { // VERTICAL
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                    spanGroupIndex , 1,
                    glp.getSpanIndex(), glp.getSpanSize(),
                    mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
        }
    
public voidonItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)

        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidonItemsChanged(RecyclerView recyclerView)

        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidonItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)

        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidonItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)

        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidonItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount)

        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)

        if (state.isPreLayout()) {
            cachePreLayoutSpanMapping();
        }
        super.onLayoutChildren(recycler, state);
        if (DEBUG) {
            validateChildOrder();
        }
        clearPreLayoutSpanMappingCache();
    
public voidsetSpanCount(int spanCount)
Sets the number of spans to be laid out.

If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns. If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.

param
spanCount The total number of spans in the grid
see
#getSpanCount()

        if (spanCount == mSpanCount) {
            return;
        }
        if (spanCount < 1) {
            throw new IllegalArgumentException("Span count should be at least 1. Provided "
                    + spanCount);
        }
        mSpanCount = spanCount;
        mSpanSizeLookup.invalidateSpanIndexCache();
    
public voidsetSpanSizeLookup(android.support.v7.widget.GridLayoutManager$SpanSizeLookup spanSizeLookup)
Sets the source to get the number of spans occupied by each item in the adapter.

param
spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans occupied by each item

        mSpanSizeLookup = spanSizeLookup;
    
public voidsetStackFromEnd(boolean stackFromEnd)
stackFromEnd is not supported by GridLayoutManager. Consider using {@link #setReverseLayout(boolean)}.

        if (stackFromEnd) {
            throw new UnsupportedOperationException(
                    "GridLayoutManager does not support stack from end."
                            + " Consider using reverse layout");
        }
        super.setStackFromEnd(false);
    
public booleansupportsPredictiveItemAnimations()

        return mPendingSavedState == null;
    
private voidupdateMeasurements()

        int totalSpace;
        if (getOrientation() == VERTICAL) {
            totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
        } else {
            totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
        }
        mSizePerSpan = totalSpace / mSpanCount;
    
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;