public class

GridLayoutManager

extends LinearLayoutManager

 java.lang.Object

androidx.recyclerview.widget.RecyclerView.LayoutManager

androidx.recyclerview.widget.LinearLayoutManager

↳androidx.recyclerview.widget.GridLayoutManager

Gradle dependencies

compile group: 'androidx.recyclerview', name: 'recyclerview', version: '1.3.0-alpha02'

  • groupId: androidx.recyclerview
  • artifactId: recyclerview
  • version: 1.3.0-alpha02

Artifact androidx.recyclerview:recyclerview:1.3.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.recyclerview:recyclerview com.android.support:recyclerview-v7

Androidx class mapping:

androidx.recyclerview.widget.GridLayoutManager android.support.v7.widget.GridLayoutManager

Overview

A 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 GridLayoutManager.SpanSizeLookup instance via GridLayoutManager.setSpanSizeLookup(GridLayoutManager.SpanSizeLookup).

Summary

Fields
public static final intDEFAULT_SPAN_COUNT

from LinearLayoutManagerHORIZONTAL, INVALID_OFFSET, VERTICAL
Constructors
publicGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Constructor used when layout manager is set in XML by RecyclerView attribute "layoutManager".

publicGridLayoutManager(Context context, int spanCount)

Creates a vertical GridLayoutManager

publicGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)

Methods
public booleancheckLayoutParams(RecyclerView.LayoutParams lp)

Determines the validity of the supplied LayoutParams object.

public intcomputeHorizontalScrollOffset(RecyclerView.State state)

Override this method if you want to support scroll bars.

public intcomputeHorizontalScrollRange(RecyclerView.State state)

Override this method if you want to support scroll bars.

public intcomputeVerticalScrollOffset(RecyclerView.State state)

Override this method if you want to support scroll bars.

public intcomputeVerticalScrollRange(RecyclerView.State state)

Override this method if you want to support scroll bars.

public RecyclerView.LayoutParamsgenerateDefaultLayoutParams()

public RecyclerView.LayoutParamsgenerateLayoutParams(Context c, AttributeSet attrs)

Create a LayoutParams object suitable for this LayoutManager from an inflated layout resource.

public RecyclerView.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams lp)

Create a LayoutParams object suitable for this LayoutManager, copying relevant values from the supplied LayoutParams object if possible.

public intgetColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

Returns the number of columns for accessibility.

public intgetRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

Returns the number of rows for accessibility.

public intgetSpanCount()

Returns the number of spans laid out by this grid.

public GridLayoutManager.SpanSizeLookupgetSpanSizeLookup()

Returns the current GridLayoutManager.SpanSizeLookup used by the GridLayoutManager.

public booleanisUsingSpansToEstimateScrollbarDimensions()

Returns true if the scroll offset and scroll range calculations take account of span information.

public ViewonFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state)

Called when searching for a focusable view in the given direction has failed for the current content of the RecyclerView.

public voidonInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)

Called by the AccessibilityDelegate when the accessibility information for a specific item should be populated.

public voidonItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)

Called when items have been added to the adapter.

public voidonItemsChanged(RecyclerView recyclerView)

Called in response to a call to RecyclerView.Adapter.notifyDataSetChanged() or RecyclerView.swapAdapter(RecyclerView.Adapter, boolean) ()} and signals that the the entire data set has changed.

public voidonItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)

Called when an item is moved withing the adapter.

public voidonItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)

Called when items have been removed from the adapter.

public voidonItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, java.lang.Object payload)

Called when items have been changed in the adapter and with optional payload.

public voidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)

public voidonLayoutCompleted(RecyclerView.State state)

Called after a full layout calculation is finished.

public intscrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)

public intscrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)

public voidsetMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)

Sets the measured dimensions from the given bounding box of the children and the measurement specs that were passed into RecyclerView.onMeasure(int, int).

public voidsetSpanCount(int spanCount)

Sets the number of spans to be laid out.

public voidsetSpanSizeLookup(GridLayoutManager.SpanSizeLookup spanSizeLookup)

Sets the source to get the number of spans occupied by each item in the adapter.

public voidsetStackFromEnd(boolean stackFromEnd)

stackFromEnd is not supported by GridLayoutManager.

public voidsetUsingSpansToEstimateScrollbarDimensions(boolean useSpansToEstimateScrollBarDimensions)

When this flag is set, the scroll offset and scroll range calculations will take account of span information.

public booleansupportsPredictiveItemAnimations()

Returns whether this LayoutManager supports "predictive item animations".

from LinearLayoutManagerassertNotInLayoutOrScroll, calculateExtraLayoutSpace, canScrollHorizontally, canScrollVertically, collectAdjacentPrefetchPositions, collectInitialPrefetchPositions, computeHorizontalScrollExtent, computeScrollVectorForPosition, computeVerticalScrollExtent, findFirstCompletelyVisibleItemPosition, findFirstVisibleItemPosition, findLastCompletelyVisibleItemPosition, findLastVisibleItemPosition, findViewByPosition, getExtraLayoutSpace, getInitialPrefetchItemCount, getOrientation, getRecycleChildrenOnDetach, getReverseLayout, getStackFromEnd, isAutoMeasureEnabled, isLayoutRTL, isSmoothScrollbarEnabled, onDetachedFromWindow, onInitializeAccessibilityEvent, onRestoreInstanceState, onSaveInstanceState, prepareForDrop, scrollToPosition, scrollToPositionWithOffset, setInitialPrefetchItemCount, setOrientation, setRecycleChildrenOnDetach, setReverseLayout, setSmoothScrollbarEnabled, smoothScrollToPosition
from RecyclerView.LayoutManageraddDisappearingView, addDisappearingView, addView, addView, assertInLayoutOrScroll, attachView, attachView, attachView, calculateItemDecorationsForChild, chooseSize, detachAndScrapAttachedViews, detachAndScrapView, detachAndScrapViewAt, detachView, detachViewAt, endAnimation, findContainingItemView, getBaseline, getBottomDecorationHeight, getChildAt, getChildCount, getChildMeasureSpec, getChildMeasureSpec, getClipToPadding, getDecoratedBottom, getDecoratedBoundsWithMargins, getDecoratedLeft, getDecoratedMeasuredHeight, getDecoratedMeasuredWidth, getDecoratedRight, getDecoratedTop, getFocusedChild, getHeight, getHeightMode, getItemCount, getItemViewType, getLayoutDirection, getLeftDecorationWidth, getMinimumHeight, getMinimumWidth, getPaddingBottom, getPaddingEnd, getPaddingLeft, getPaddingRight, getPaddingStart, getPaddingTop, getPosition, getProperties, getRightDecorationWidth, getSelectionModeForAccessibility, getTopDecorationHeight, getTransformedBoundingBox, getWidth, getWidthMode, hasFocus, ignoreView, isAttachedToWindow, isFocused, isItemPrefetchEnabled, isLayoutHierarchical, isMeasurementCacheEnabled, isSmoothScrolling, isViewPartiallyVisible, layoutDecorated, layoutDecoratedWithMargins, measureChild, measureChildWithMargins, moveView, offsetChildrenHorizontal, offsetChildrenVertical, onAdapterChanged, onAddFocusables, onAttachedToWindow, onDetachedFromWindow, onInitializeAccessibilityEvent, onInitializeAccessibilityNodeInfo, onInterceptFocusSearch, onItemsUpdated, onMeasure, onRequestChildFocus, onRequestChildFocus, onScrollStateChanged, performAccessibilityAction, performAccessibilityActionForItem, postOnAnimation, removeAllViews, removeAndRecycleAllViews, removeAndRecycleView, removeAndRecycleViewAt, removeCallbacks, removeDetachedView, removeView, removeViewAt, requestChildRectangleOnScreen, requestChildRectangleOnScreen, requestLayout, requestSimpleAnimationsInNextLayout, setAutoMeasureEnabled, setItemPrefetchEnabled, setMeasuredDimension, setMeasurementCacheEnabled, startSmoothScroll, stopIgnoringView
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int DEFAULT_SPAN_COUNT

Constructors

public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Constructor used when layout manager is set in XML by RecyclerView attribute "layoutManager". If spanCount is not specified in the XML, it defaults to a single column.

public GridLayoutManager(Context context, int spanCount)

Creates a vertical GridLayoutManager

Parameters:

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

public GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)

Parameters:

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

Methods

public void setStackFromEnd(boolean stackFromEnd)

stackFromEnd is not supported by GridLayoutManager. Consider using LinearLayoutManager.setReverseLayout(boolean).

public int getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

Returns the number of rows for accessibility.

Default implementation returns the number of items in the adapter if LayoutManager supports vertical scrolling or 1 if LayoutManager does not support vertical scrolling.

Parameters:

recycler: The Recycler that can be used to convert view positions into adapter positions
state: The current state of RecyclerView

Returns:

The number of rows in LayoutManager for accessibility.

public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)

Returns the number of columns for accessibility.

Default implementation returns the number of items in the adapter if LayoutManager supports horizontal scrolling or 1 if LayoutManager does not support horizontal scrolling.

Parameters:

recycler: The Recycler that can be used to convert view positions into adapter positions
state: The current state of RecyclerView

Returns:

The number of rows in LayoutManager for accessibility.

public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)

Called by the AccessibilityDelegate when the accessibility information for a specific item should be populated.

Default implementation adds basic positioning information about the item.

Parameters:

recycler: The Recycler that can be used to convert view positions into adapter positions
state: The current state of RecyclerView
host: The child for which accessibility node info should be populated
info: The info to fill out about the item

See also: android.widget.AbsListView

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)

public void onLayoutCompleted(RecyclerView.State state)

Called after a full layout calculation is finished. The layout calculation may include multiple RecyclerView.LayoutManager calls due to animations or layout measurement but it will include only one RecyclerView.LayoutManager call. This method will be called at the end of View call.

This is a good place for the LayoutManager to do some cleanup like pending scroll position, saved state etc.

Parameters:

state: Transient state of RecyclerView

public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)

Called when items have been added to the adapter. The LayoutManager may choose to requestLayout if the inserted items would require refreshing the currently visible set of child views. (e.g. currently empty space would be filled by appended items, etc.)

public void onItemsChanged(RecyclerView recyclerView)

Called in response to a call to RecyclerView.Adapter.notifyDataSetChanged() or RecyclerView.swapAdapter(RecyclerView.Adapter, boolean) ()} and signals that the the entire data set has changed.

public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)

Called when items have been removed from the adapter.

public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, java.lang.Object payload)

Called when items have been changed in the adapter and with optional payload. Default implementation calls RecyclerView.LayoutManager.onItemsUpdated(RecyclerView, int, int).

public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)

Called when an item is moved withing the adapter.

Note that, an item may also change position in response to another ADD/REMOVE/MOVE operation. This callback is only called if and only if RecyclerView.Adapter.notifyItemMoved(int, int) is called.

public RecyclerView.LayoutParams generateDefaultLayoutParams()

public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs)

Create a LayoutParams object suitable for this LayoutManager from an inflated layout resource.

Important: if you use your own custom LayoutParams type you must also override RecyclerView.LayoutManager, RecyclerView.LayoutManager and RecyclerView.LayoutManager.

Parameters:

c: Context for obtaining styled attributes
attrs: AttributeSet describing the supplied arguments

Returns:

a new LayoutParams object

public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp)

Create a LayoutParams object suitable for this LayoutManager, copying relevant values from the supplied LayoutParams object if possible.

Important: if you use your own custom LayoutParams type you must also override RecyclerView.LayoutManager, RecyclerView.LayoutManager and RecyclerView.LayoutManager.

Parameters:

lp: Source LayoutParams object to copy values from

Returns:

a new LayoutParams object

public boolean checkLayoutParams(RecyclerView.LayoutParams lp)

Determines the validity of the supplied LayoutParams object.

This should check to make sure that the object is of the correct type and all values are within acceptable ranges. The default implementation returns true for non-null params.

Parameters:

lp: LayoutParams object to check

Returns:

true if this LayoutParams object is valid, false otherwise

public void setSpanSizeLookup(GridLayoutManager.SpanSizeLookup spanSizeLookup)

Sets the source to get the number of spans occupied by each item in the adapter.

Parameters:

spanSizeLookup: GridLayoutManager.SpanSizeLookup instance to be used to query number of spans occupied by each item

public GridLayoutManager.SpanSizeLookup getSpanSizeLookup()

Returns the current GridLayoutManager.SpanSizeLookup used by the GridLayoutManager.

Returns:

The current GridLayoutManager.SpanSizeLookup used by the GridLayoutManager.

public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)

Sets the measured dimensions from the given bounding box of the children and the measurement specs that were passed into RecyclerView.onMeasure(int, int). It is only called if a LayoutManager returns true from RecyclerView.LayoutManager.isAutoMeasureEnabled() and it is called after the RecyclerView calls RecyclerView.LayoutManager in the execution of RecyclerView.onMeasure(int, int).

This method must call RecyclerView.LayoutManager.setMeasuredDimension(int, int).

The default implementation adds the RecyclerView's padding to the given bounding box then caps the value to be within the given measurement specs.

Parameters:

childrenBounds: The bounding box of all children
wSpec: The widthMeasureSpec that was passed into the RecyclerView.
hSpec: The heightMeasureSpec that was passed into the RecyclerView.

See also: RecyclerView.LayoutManager.isAutoMeasureEnabled(), RecyclerView.LayoutManager.setMeasuredDimension(int, int)

public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)

public int getSpanCount()

Returns the number of spans laid out by this grid.

Returns:

The number of spans

See also: GridLayoutManager.setSpanCount(int)

public void setSpanCount(int spanCount)

Sets the number of spans to be laid out.

If LinearLayoutManager.getOrientation() is LinearLayoutManager.VERTICAL, this is the number of columns. If LinearLayoutManager.getOrientation() is LinearLayoutManager.HORIZONTAL, this is the number of rows.

Parameters:

spanCount: The total number of spans in the grid

See also: GridLayoutManager.getSpanCount()

public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state)

Called when searching for a focusable view in the given direction has failed for the current content of the RecyclerView.

This is the LayoutManager's opportunity to populate views in the given direction to fulfill the request if it can. The LayoutManager should attach and return the view to be focused, if a focusable view in the given direction is found. Otherwise, if all the existing (or the newly populated views) are unfocusable, it returns the next unfocusable view to become visible on the screen. This unfocusable view is typically the first view that's either partially or fully out of RV's padded bounded area in the given direction. The default implementation returns null.

Parameters:

focused: The currently focused view
direction: One of View, View, View, View, View, View or 0 for not applicable
recycler: The recycler to use for obtaining views for currently offscreen items
state: Transient state of RecyclerView

Returns:

The chosen view to be focused if a focusable view is found, otherwise an unfocusable view to become visible onto the screen, else null.

public boolean supportsPredictiveItemAnimations()

Returns whether this LayoutManager supports "predictive item animations".

"Predictive item animations" are automatically created animations that show where items came from, and where they are going to, as items are added, removed, or moved within a layout.

A LayoutManager wishing to support predictive item animations must override this method to return true (the default implementation returns false) and must obey certain behavioral contracts outlined in RecyclerView.LayoutManager.

Whether item animations actually occur in a RecyclerView is actually determined by both the return value from this method and the ItemAnimator set on the RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this method returns false, then only "simple item animations" will be enabled in the RecyclerView, in which views whose position are changing are simply faded in/out. If the RecyclerView has a non-null ItemAnimator and this method returns true, then predictive item animations will be enabled in the RecyclerView.

Returns:

true if this LayoutManager supports predictive item animations, false otherwise.

public int computeHorizontalScrollRange(RecyclerView.State state)

Override this method if you want to support scroll bars.

Read RecyclerView.computeHorizontalScrollRange() for details.

Default implementation returns 0.

Parameters:

state: Current State of RecyclerView where you can find total item count

Returns:

The total horizontal range represented by the vertical scrollbar

See also: RecyclerView.computeHorizontalScrollRange()

public int computeVerticalScrollRange(RecyclerView.State state)

Override this method if you want to support scroll bars.

Read RecyclerView.computeVerticalScrollRange() for details.

Default implementation returns 0.

Parameters:

state: Current State of RecyclerView where you can find total item count

Returns:

The total vertical range represented by the vertical scrollbar

See also: RecyclerView.computeVerticalScrollRange()

public int computeHorizontalScrollOffset(RecyclerView.State state)

Override this method if you want to support scroll bars.

Read RecyclerView.computeHorizontalScrollOffset() for details.

Default implementation returns 0.

Parameters:

state: Current State of RecyclerView where you can find total item count

Returns:

The horizontal offset of the scrollbar's thumb

See also: RecyclerView.computeHorizontalScrollOffset()

public int computeVerticalScrollOffset(RecyclerView.State state)

Override this method if you want to support scroll bars.

Read RecyclerView.computeVerticalScrollOffset() for details.

Default implementation returns 0.

Parameters:

state: Current State of RecyclerView where you can find total item count

Returns:

The vertical offset of the scrollbar's thumb

See also: RecyclerView.computeVerticalScrollOffset()

public void setUsingSpansToEstimateScrollbarDimensions(boolean useSpansToEstimateScrollBarDimensions)

When this flag is set, the scroll offset and scroll range calculations will take account of span information.

This is will increase the accuracy of the scroll bar's size and offset but will require more calls to GridLayoutManager.SpanSizeLookup.getSpanGroupIndex(int, int)".

This additional accuracy may or may not be needed, depending on the characteristics of your layout. You will likely benefit from this accuracy when:

If you decide to enable this feature, you should be sure that calls to GridLayoutManager.SpanSizeLookup.getSpanGroupIndex(int, int) are fast, that set span group index caching is set to true via a call to .

public boolean isUsingSpansToEstimateScrollbarDimensions()

Returns true if the scroll offset and scroll range calculations take account of span information. See GridLayoutManager.setUsingSpansToEstimateScrollbarDimensions(boolean) for more information on this topic. Defaults to false.

Returns:

true if the scroll offset and scroll range calculations take account of span information.

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.recyclerview.widget;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;

import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;

import java.util.Arrays;

/**
 * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
 * <p>
 * By default, each item occupies 1 span. You can change it by providing a custom
 * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
 */
public class GridLayoutManager extends LinearLayoutManager {

    private static final boolean DEBUG = false;
    private static final String TAG = "GridLayoutManager";
    public static final int DEFAULT_SPAN_COUNT = -1;
    /**
     * Span size have been changed but we've not done a new layout calculation.
     */
    boolean mPendingSpanCountChange = false;
    int mSpanCount = DEFAULT_SPAN_COUNT;
    /**
     * Right borders for each span.
     * <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
     * and end is {@link #mCachedBorders}[i].
     */
    int [] mCachedBorders;
    /**
     * Temporary array to keep views in layoutChunk method
     */
    View[] mSet;
    final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
    final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
    SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
    // re-used variable to acquire decor insets from RecyclerView
    final Rect mDecorInsets = new Rect();

    private boolean mUsingSpansToEstimateScrollBarDimensions;

    /**
     * Constructor used when layout manager is set in XML by RecyclerView attribute
     * "layoutManager". If spanCount is not specified in the XML, it defaults to a
     * single column.
     *
     * {@link androidx.recyclerview.R.attr#spanCount}
     */
    public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
                             int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
        setSpanCount(properties.spanCount);
    }

    /**
     * Creates a vertical GridLayoutManager
     *
     * @param context Current context, will be used to access resources.
     * @param spanCount The number of columns in the grid
     */
    public GridLayoutManager(Context context, int spanCount) {
        super(context);
        setSpanCount(spanCount);
    }

    /**
     * @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.
     */
    public GridLayoutManager(Context context, int spanCount,
            @RecyclerView.Orientation int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        setSpanCount(spanCount);
    }

    /**
     * stackFromEnd is not supported by GridLayoutManager. Consider using
     * {@link #setReverseLayout(boolean)}.
     */
    @Override
    public void setStackFromEnd(boolean stackFromEnd) {
        if (stackFromEnd) {
            throw new UnsupportedOperationException(
                    "GridLayoutManager does not support stack from end."
                            + " Consider using reverse layout");
        }
        super.setStackFromEnd(false);
    }

    @Override
    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return mSpanCount;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }

        // Row count is one more than the last item's row index.
        return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
    }

    @Override
    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return mSpanCount;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }

        // Column count is one more than the last item's column index.
        return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
    }

    @Override
    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
            RecyclerView.State state, View host, 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, false, false));
        } else { // VERTICAL
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                    spanGroupIndex , 1,
                    glp.getSpanIndex(), glp.getSpanSize(), false, false));
        }
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout()) {
            cachePreLayoutSpanMapping();
        }
        super.onLayoutChildren(recycler, state);
        if (DEBUG) {
            validateChildOrder();
        }
        clearPreLayoutSpanMappingCache();
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        mPendingSpanCountChange = false;
    }

    private void clearPreLayoutSpanMappingCache() {
        mPreLayoutSpanSizeCache.clear();
        mPreLayoutSpanIndexCache.clear();
    }

    private void cachePreLayoutSpanMapping() {
        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());
        }
    }

    @Override
    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
        mSpanSizeLookup.invalidateSpanIndexCache();
        mSpanSizeLookup.invalidateSpanGroupIndexCache();
    }

    @Override
    public void onItemsChanged(RecyclerView recyclerView) {
        mSpanSizeLookup.invalidateSpanIndexCache();
        mSpanSizeLookup.invalidateSpanGroupIndexCache();
    }

    @Override
    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
        mSpanSizeLookup.invalidateSpanIndexCache();
        mSpanSizeLookup.invalidateSpanGroupIndexCache();
    }

    @Override
    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
            Object payload) {
        mSpanSizeLookup.invalidateSpanIndexCache();
        mSpanSizeLookup.invalidateSpanGroupIndexCache();
    }

    @Override
    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
        mSpanSizeLookup.invalidateSpanIndexCache();
        mSpanSizeLookup.invalidateSpanGroupIndexCache();
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
        } else {
            return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }

    @Override
    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
        return new LayoutParams(c, attrs);
    }

    @Override
    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        if (lp instanceof ViewGroup.MarginLayoutParams) {
            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
        } else {
            return new LayoutParams(lp);
        }
    }

    @Override
    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
        return lp instanceof LayoutParams;
    }

    /**
     * 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
     */
    public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
        mSpanSizeLookup = spanSizeLookup;
    }

    /**
     * Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
     *
     * @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
     */
    public SpanSizeLookup getSpanSizeLookup() {
        return mSpanSizeLookup;
    }

    private void updateMeasurements() {
        int totalSpace;
        if (getOrientation() == VERTICAL) {
            totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
        } else {
            totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
        }
        calculateItemBorders(totalSpace);
    }

    @Override
    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
        if (mCachedBorders == null) {
            super.setMeasuredDimension(childrenBounds, wSpec, hSpec);
        }
        final int width, height;
        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
        final int verticalPadding = getPaddingTop() + getPaddingBottom();
        if (mOrientation == VERTICAL) {
            final int usedHeight = childrenBounds.height() + verticalPadding;
            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
            width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding,
                    getMinimumWidth());
        } else {
            final int usedWidth = childrenBounds.width() + horizontalPadding;
            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
            height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding,
                    getMinimumHeight());
        }
        setMeasuredDimension(width, height);
    }

    /**
     * @param totalSpace Total available space after padding is removed
     */
    private void calculateItemBorders(int totalSpace) {
        mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace);
    }

    /**
     * @param cachedBorders The out array
     * @param spanCount number of spans
     * @param totalSpace total available space after padding is removed
     * @return The updated array. Might be the same instance as the provided array if its size
     * has not changed.
     */
    static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
        if (cachedBorders == null || cachedBorders.length != spanCount + 1
                || cachedBorders[cachedBorders.length - 1] != totalSpace) {
            cachedBorders = new int[spanCount + 1];
        }
        cachedBorders[0] = 0;
        int sizePerSpan = totalSpace / spanCount;
        int sizePerSpanRemainder = totalSpace % spanCount;
        int consumedPixels = 0;
        int additionalSize = 0;
        for (int i = 1; i <= spanCount; i++) {
            int itemSize = sizePerSpan;
            additionalSize += sizePerSpanRemainder;
            if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {
                itemSize += 1;
                additionalSize -= spanCount;
            }
            consumedPixels += itemSize;
            cachedBorders[i] = consumedPixels;
        }
        return cachedBorders;
    }

    int getSpaceForSpanRange(int startSpan, int spanSize) {
        if (mOrientation == VERTICAL && isLayoutRTL()) {
            return mCachedBorders[mSpanCount - startSpan]
                    - mCachedBorders[mSpanCount - startSpan - spanSize];
        } else {
            return mCachedBorders[startSpan + spanSize] - mCachedBorders[startSpan];
        }
    }

    @Override
    void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
                       AnchorInfo anchorInfo, int itemDirection) {
        super.onAnchorReady(recycler, state, anchorInfo, itemDirection);
        updateMeasurements();
        if (state.getItemCount() > 0 && !state.isPreLayout()) {
            ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection);
        }
        ensureViewSet();
    }

    private void ensureViewSet() {
        if (mSet == null || mSet.length != mSpanCount) {
            mSet = new View[mSpanCount];
        }
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        updateMeasurements();
        ensureViewSet();
        return super.scrollHorizontallyBy(dx, recycler, state);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        updateMeasurements();
        ensureViewSet();
        return super.scrollVerticallyBy(dy, recycler, state);
    }

    private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler,
            RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
        final boolean layingOutInPrimaryDirection =
                itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
        int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
        if (layingOutInPrimaryDirection) {
            // choose span 0
            while (span > 0 && anchorInfo.mPosition > 0) {
                anchorInfo.mPosition--;
                span = getSpanIndex(recycler, state, anchorInfo.mPosition);
            }
        } else {
            // choose the max span we can get. hopefully last one
            final int indexLimit = state.getItemCount() - 1;
            int pos = anchorInfo.mPosition;
            int bestSpan = span;
            while (pos < indexLimit) {
                int next = getSpanIndex(recycler, state, pos + 1);
                if (next > bestSpan) {
                    pos += 1;
                    bestSpan = next;
                } else {
                    break;
                }
            }
            anchorInfo.mPosition = pos;
        }
    }

    @Override
    View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
            boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) {

        int start = 0;
        int end = getChildCount();
        int diff = 1;
        if (traverseChildrenInReverseOrder) {
            start = getChildCount() - 1;
            end = -1;
            diff = -1;
        }

        int itemCount = state.getItemCount();

        ensureLayoutState();
        View invalidMatch = null;
        View outOfBoundsMatch = null;

        final int boundsStart = mOrientationHelper.getStartAfterPadding();
        final int boundsEnd = mOrientationHelper.getEndAfterPadding();

        for (int i = start; i != end; i += diff) {
            final View view = getChildAt(i);
            final int position = getPosition(view);
            if (position >= 0 && position < itemCount) {
                final int span = getSpanIndex(recycler, state, position);
                if (span != 0) {
                    continue;
                }
                if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
                    if (invalidMatch == null) {
                        invalidMatch = view; // removed item, least preferred
                    }
                } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd
                        || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
                    if (outOfBoundsMatch == null) {
                        outOfBoundsMatch = view; // item is not visible, less preferred
                    }
                } else {
                    return view;
                }
            }
        }
        return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
    }

    private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
            int viewPosition) {
        if (!state.isPreLayout()) {
            return mSpanSizeLookup.getCachedSpanGroupIndex(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.getCachedSpanGroupIndex(adapterPosition, mSpanCount);
    }

    private int getSpanIndex(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 int getSpanSize(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);
    }

    @Override
    void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
            LayoutPrefetchRegistry layoutPrefetchRegistry) {
        int remainingSpan = mSpanCount;
        int count = 0;
        while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
            final int pos = layoutState.mCurrentPosition;
            layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
            final int spanSize = mSpanSizeLookup.getSpanSize(pos);
            remainingSpan -= spanSize;
            layoutState.mCurrentPosition += layoutState.mItemDirection;
            count++;
        }
    }

    @Override
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        final int otherDirSpecMode = mOrientationHelper.getModeInOther();
        final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
        final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0;
        // if grid layout's dimensions are not specified, let the new row change the measurements
        // This is not perfect since we not covering all rows but still solves an important case
        // where they may have a header row which should be laid out according to children.
        if (flexibleInOtherDir) {
            updateMeasurements(); //  reset measurements
        }
        final boolean layingOutInPrimaryDirection =
                layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
        int count = 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;
            }
            mSet[count] = view;
            count++;
        }

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

        int maxSize = 0;
        float maxSizeInOther = 0; // use a float to get size per span

        // we should assign spans before item decor offsets are calculated
        assignSpans(recycler, state, count, 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);
                }
            }
            calculateItemDecorationsForChild(view, mDecorInsets);

            measureChild(view, otherDirSpecMode, false);
            final int size = mOrientationHelper.getDecoratedMeasurement(view);
            if (size > maxSize) {
                maxSize = size;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view)
                    / lp.mSpanSize;
            if (otherSize > maxSizeInOther) {
                maxSizeInOther = otherSize;
            }
        }
        if (flexibleInOtherDir) {
            // re-distribute columns
            guessMeasurement(maxSizeInOther, currentOtherDirSize);
            // now we should re-measure any item that was match parent.
            maxSize = 0;
            for (int i = 0; i < count; i++) {
                View view = mSet[i];
                measureChild(view, View.MeasureSpec.EXACTLY, true);
                final int size = mOrientationHelper.getDecoratedMeasurement(view);
                if (size > maxSize) {
                    maxSize = size;
                }
            }
        }

        // Views that did not measure the maxSize has to be re-measured
        // We will stop doing this once we introduce Gravity in the GLM layout params
        for (int i = 0; i < count; i++) {
            final View view = mSet[i];
            if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
                final Rect decorInsets = lp.mDecorInsets;
                final int verticalInsets = decorInsets.top + decorInsets.bottom
                        + lp.topMargin + lp.bottomMargin;
                final int horizontalInsets = decorInsets.left + decorInsets.right
                        + lp.leftMargin + lp.rightMargin;
                final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
                final int wSpec;
                final int hSpec;
                if (mOrientation == VERTICAL) {
                    wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                            horizontalInsets, lp.width, false);
                    hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
                            View.MeasureSpec.EXACTLY);
                } else {
                    wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets,
                            View.MeasureSpec.EXACTLY);
                    hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                            verticalInsets, lp.height, false);
                }
                measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
            }
        }

        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) {
                if (isLayoutRTL()) {
                    right = getPaddingLeft() + mCachedBorders[mSpanCount - params.mSpanIndex];
                    left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
                } else {
                    left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
                    right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
                }
            } else {
                top = getPaddingTop() + mCachedBorders[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.
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            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.hasFocusable();
        }
        Arrays.fill(mSet, null);
    }

    /**
     * Measures a child with currently known information. This is not necessarily the child's final
     * measurement. (see fillChunk for details).
     *
     * @param view The child view to be measured
     * @param otherDirParentSpecMode The RV measure spec that should be used in the secondary
     *                               orientation
     * @param alreadyMeasured True if we've already measured this view once
     */
    private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) {
        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
        final Rect decorInsets = lp.mDecorInsets;
        final int verticalInsets = decorInsets.top + decorInsets.bottom
                + lp.topMargin + lp.bottomMargin;
        final int horizontalInsets = decorInsets.left + decorInsets.right
                + lp.leftMargin + lp.rightMargin;
        final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
        final int wSpec;
        final int hSpec;
        if (mOrientation == VERTICAL) {
            wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                    horizontalInsets, lp.width, false);
            hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),
                    verticalInsets, lp.height, true);
        } else {
            hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                    verticalInsets, lp.height, false);
            wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(),
                    horizontalInsets, lp.width, true);
        }
        measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured);
    }

    /**
     * This is called after laying out a row (if vertical) or a column (if horizontal) when the
     * RecyclerView does not have exact measurement specs.
     * <p>
     * Here we try to assign a best guess width or height and re-do the layout to update other
     * views that wanted to MATCH_PARENT in the non-scroll orientation.
     *
     * @param maxSizeInOther The maximum size per span ratio from the measurement of the children.
     * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below.
     */
    private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) {
        final int contentSize = Math.round(maxSizeInOther * mSpanCount);
        // always re-calculate because borders were stretched during the fill
        calculateItemBorders(Math.max(contentSize, currentOtherDirSize));
    }

    private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
            boolean alreadyMeasured) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
        final boolean measure;
        if (alreadyMeasured) {
            measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
        } else {
            measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
        }
        if (measure) {
            child.measure(widthSpec, heightSpec);
        }
    }

    private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
            boolean layingOutInPrimaryDirection) {
        // spans are always assigned from 0 to N no matter if it is RTL or not.
        // RTL is used only when positioning the view.
        int span, 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;
        }
        span = 0;
        for (int i = start; i != end; i += diff) {
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
            params.mSpanIndex = span;
            span += params.mSpanSize;
        }
    }

    /**
     * Returns the number of spans laid out by this grid.
     *
     * @return The number of spans
     * @see #setSpanCount(int)
     */
    public int getSpanCount() {
        return mSpanCount;
    }

    /**
     * Sets the number of spans to be laid out.
     * <p>
     * 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()
     */
    public void setSpanCount(int spanCount) {
        if (spanCount == mSpanCount) {
            return;
        }
        mPendingSpanCountChange = true;
        if (spanCount < 1) {
            throw new IllegalArgumentException("Span count should be at least 1. Provided "
                    + spanCount);
        }
        mSpanCount = spanCount;
        mSpanSizeLookup.invalidateSpanIndexCache();
        requestLayout();
    }

    /**
     * A helper class to provide the number of spans each item occupies.
     * <p>
     * Default implementation sets each item to occupy exactly 1 span.
     *
     * @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
     */
    public abstract static class SpanSizeLookup {

        final SparseIntArray mSpanIndexCache = new SparseIntArray();
        final SparseIntArray mSpanGroupIndexCache = new SparseIntArray();

        private boolean mCacheSpanIndices = false;
        private boolean mCacheSpanGroupIndices = false;

        /**
         * Returns the number of span occupied by the item at <code>position</code>.
         *
         * @param position The adapter position of the item
         * @return The number of spans occupied by the item at the provided position
         */
        public abstract int getSpanSize(int position);

        /**
         * Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
         * not. By default these values are not cached. If you are not overriding
         * {@link #getSpanIndex(int, int)} with something highly performant, you should set this
         * to true for better performance.
         *
         * @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
         */
        public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
            if (!cacheSpanIndices) {
                mSpanGroupIndexCache.clear();
            }
            mCacheSpanIndices = cacheSpanIndices;
        }

        /**
         * Sets whether the results of {@link #getSpanGroupIndex(int, int)} method should be cached
         * or not. By default these values are not cached. If you are not overriding
         * {@link #getSpanGroupIndex(int, int)} with something highly performant, and you are using
         * spans to calculate scrollbar offset and range, you should set this to true for better
         * performance.
         *
         * @param cacheSpanGroupIndices Whether results of getGroupSpanIndex should be cached or
         *                              not.
         */
        public void setSpanGroupIndexCacheEnabled(boolean cacheSpanGroupIndices)  {
            if (!cacheSpanGroupIndices) {
                mSpanGroupIndexCache.clear();
            }
            mCacheSpanGroupIndices = cacheSpanGroupIndices;
        }

        /**
         * Clears the span index cache. GridLayoutManager automatically calls this method when
         * adapter changes occur.
         */
        public void invalidateSpanIndexCache() {
            mSpanIndexCache.clear();
        }

        /**
         * Clears the span group index cache. GridLayoutManager automatically calls this method
         * when adapter changes occur.
         */
        public void invalidateSpanGroupIndexCache() {
            mSpanGroupIndexCache.clear();
        }

        /**
         * Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
         *
         * @return True if results of {@link #getSpanIndex(int, int)} are cached.
         */
        public boolean isSpanIndexCacheEnabled() {
            return mCacheSpanIndices;
        }

        /**
         * Returns whether results of {@link #getSpanGroupIndex(int, int)} method are cached or not.
         *
         * @return True if results of {@link #getSpanGroupIndex(int, int)} are cached.
         */
        public boolean isSpanGroupIndexCacheEnabled() {
            return mCacheSpanGroupIndices;
        }

        int getCachedSpanIndex(int position, int spanCount) {
            if (!mCacheSpanIndices) {
                return getSpanIndex(position, spanCount);
            }
            final int existing = mSpanIndexCache.get(position, -1);
            if (existing != -1) {
                return existing;
            }
            final int value = getSpanIndex(position, spanCount);
            mSpanIndexCache.put(position, value);
            return value;
        }

        int getCachedSpanGroupIndex(int position, int spanCount) {
            if (!mCacheSpanGroupIndices) {
                return getSpanGroupIndex(position, spanCount);
            }
            final int existing = mSpanGroupIndexCache.get(position, -1);
            if (existing != -1) {
                return existing;
            }
            final int value = getSpanGroupIndex(position, spanCount);
            mSpanGroupIndexCache.put(position, value);
            return value;
        }

        /**
         * Returns the final span index of the provided position.
         * <p>
         * If you have a faster way to calculate span index for your items, you should override
         * this method. Otherwise, you should enable span index cache
         * ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
         * disabled, default implementation traverses all items from 0 to
         * <code>position</code>. When caching is enabled, it calculates from the closest cached
         * value before the <code>position</code>.
         * <p>
         * If you override this method, you need to make sure it is consistent with
         * {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
         * each item. It is called only for the reference item and rest of the items
         * are assigned to spans based on the reference item. For example, you cannot assign a
         * position to span 2 while span 1 is empty.
         * <p>
         * Note that span offsets always start with 0 and are not affected by RTL.
         *
         * @param position  The position of the item
         * @param spanCount The total number of spans in the grid
         * @return The final span position of the item. Should be between 0 (inclusive) and
         * <code>spanCount</code>(exclusive)
         */
        public int getSpanIndex(int position, int spanCount) {
            int positionSpanSize = getSpanSize(position);
            if (positionSpanSize == spanCount) {
                return 0; // quick return for full-span items
            }
            int span = 0;
            int startPos = 0;
            // If caching is enabled, try to jump
            if (mCacheSpanIndices) {
                int prevKey = findFirstKeyLessThan(mSpanIndexCache, position);
                if (prevKey >= 0) {
                    span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
                    startPos = prevKey + 1;
                }
            }
            for (int i = startPos; i < position; i++) {
                int size = getSpanSize(i);
                span += size;
                if (span == spanCount) {
                    span = 0;
                } else if (span > spanCount) {
                    // did not fit, moving to next row / column
                    span = size;
                }
            }
            if (span + positionSpanSize <= spanCount) {
                return span;
            }
            return 0;
        }

        static int findFirstKeyLessThan(SparseIntArray cache, int position) {
            int lo = 0;
            int hi = cache.size() - 1;

            while (lo <= hi) {
                // Using unsigned shift here to divide by two because it is guaranteed to not
                // overflow.
                final int mid = (lo + hi) >>> 1;
                final int midVal = cache.keyAt(mid);
                if (midVal < position) {
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            int index = lo - 1;
            if (index >= 0 && index < cache.size()) {
                return cache.keyAt(index);
            }
            return -1;
        }

        /**
         * Returns the index of the group this position belongs.
         * <p>
         * For example, if grid has 3 columns and each item occupies 1 span, span group index
         * for item 1 will be 0, item 5 will be 1.
         *
         * @param adapterPosition The position in adapter
         * @param spanCount The total number of spans in the grid
         * @return The index of the span group including the item at the given adapter position
         */
        public int getSpanGroupIndex(int adapterPosition, int spanCount) {
            int span = 0;
            int group = 0;
            int start = 0;
            if (mCacheSpanGroupIndices) {
                // This finds the first non empty cached group cache key.
                int prevKey = findFirstKeyLessThan(mSpanGroupIndexCache, adapterPosition);
                if (prevKey != -1) {
                    group = mSpanGroupIndexCache.get(prevKey);
                    start = prevKey + 1;
                    span = getCachedSpanIndex(prevKey, spanCount) + getSpanSize(prevKey);
                    if (span == spanCount) {
                        span = 0;
                        group++;
                    }
                }
            }
            int positionSpanSize = getSpanSize(adapterPosition);
            for (int i = start; i < adapterPosition; i++) {
                int size = getSpanSize(i);
                span += size;
                if (span == spanCount) {
                    span = 0;
                    group++;
                } else if (span > spanCount) {
                    // did not fit, moving to next row / column
                    span = size;
                    group++;
                }
            }
            if (span + positionSpanSize > spanCount) {
                group++;
            }
            return group;
        }
    }

    @Override
    public View onFocusSearchFailed(View focused, int direction,
            RecyclerView.Recycler recycler, RecyclerView.State state) {
        View prevFocusedChild = findContainingItemView(focused);
        if (prevFocusedChild == null) {
            return null;
        }
        LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams();
        final int prevSpanStart = lp.mSpanIndex;
        final int prevSpanEnd = lp.mSpanIndex + lp.mSpanSize;
        View view = super.onFocusSearchFailed(focused, direction, recycler, state);
        if (view == null) {
            return null;
        }
        // LinearLayoutManager finds the last child. What we want is the child which has the same
        // spanIndex.
        final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
        final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout;
        final int start, inc, limit;
        if (ascend) {
            start = getChildCount() - 1;
            inc = -1;
            limit = -1;
        } else {
            start = 0;
            inc = 1;
            limit = getChildCount();
        }
        final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL();

        // The focusable candidate to be picked if no perfect focusable candidate is found.
        // The best focusable candidate is the one with the highest amount of span overlap with
        // the currently focused view.
        View focusableWeakCandidate = null; // somewhat matches but not strong
        int focusableWeakCandidateSpanIndex = -1;
        int focusableWeakCandidateOverlap = 0; // how many spans overlap

        // The unfocusable candidate to become visible on the screen next, if no perfect or
        // weak focusable candidates are found to receive focus next.
        // We are only interested in partially visible unfocusable views. These are views that are
        // not fully visible, that is either partially overlapping, or out-of-bounds and right below
        // or above RV's padded bounded area. The best unfocusable candidate is the one with the
        // highest amount of span overlap with the currently focused view.
        View unfocusableWeakCandidate = null; // somewhat matches but not strong
        int unfocusableWeakCandidateSpanIndex = -1;
        int unfocusableWeakCandidateOverlap = 0; // how many spans overlap

        // The span group index of the start child. This indicates the span group index of the
        // next focusable item to receive focus, if a focusable item within the same span group
        // exists. Any focusable item beyond this group index are not relevant since they
        // were already stored in the layout before onFocusSearchFailed call and were not picked
        // by the focusSearch algorithm.
        int focusableSpanGroupIndex = getSpanGroupIndex(recycler, state, start);
        for (int i = start; i != limit; i += inc) {
            int spanGroupIndex = getSpanGroupIndex(recycler, state, i);
            View candidate = getChildAt(i);
            if (candidate == prevFocusedChild) {
                break;
            }

            if (candidate.hasFocusable() && spanGroupIndex != focusableSpanGroupIndex) {
                // We are past the allowable span group index for the next focusable item.
                // The search only continues if no focusable weak candidates have been found up
                // until this point, in order to find the best unfocusable candidate to become
                // visible on the screen next.
                if (focusableWeakCandidate != null) {
                    break;
                }
                continue;
            }

            final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams();
            final int candidateStart = candidateLp.mSpanIndex;
            final int candidateEnd = candidateLp.mSpanIndex + candidateLp.mSpanSize;
            if (candidate.hasFocusable() && candidateStart == prevSpanStart
                    && candidateEnd == prevSpanEnd) {
                return candidate; // perfect match
            }
            boolean assignAsWeek = false;
            if ((candidate.hasFocusable() && focusableWeakCandidate == null)
                    || (!candidate.hasFocusable() && unfocusableWeakCandidate == null)) {
                assignAsWeek = true;
            } else {
                int maxStart = Math.max(candidateStart, prevSpanStart);
                int minEnd = Math.min(candidateEnd, prevSpanEnd);
                int overlap = minEnd - maxStart;
                if (candidate.hasFocusable()) {
                    if (overlap > focusableWeakCandidateOverlap) {
                        assignAsWeek = true;
                    } else if (overlap == focusableWeakCandidateOverlap
                            && preferLastSpan == (candidateStart
                            > focusableWeakCandidateSpanIndex)) {
                        assignAsWeek = true;
                    }
                } else if (focusableWeakCandidate == null
                        && isViewPartiallyVisible(candidate, false, true)) {
                    if (overlap > unfocusableWeakCandidateOverlap) {
                        assignAsWeek = true;
                    } else if (overlap == unfocusableWeakCandidateOverlap
                            && preferLastSpan == (candidateStart
                                    > unfocusableWeakCandidateSpanIndex)) {
                        assignAsWeek = true;
                    }
                }
            }

            if (assignAsWeek) {
                if (candidate.hasFocusable()) {
                    focusableWeakCandidate = candidate;
                    focusableWeakCandidateSpanIndex = candidateLp.mSpanIndex;
                    focusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd)
                            - Math.max(candidateStart, prevSpanStart);
                } else {
                    unfocusableWeakCandidate = candidate;
                    unfocusableWeakCandidateSpanIndex = candidateLp.mSpanIndex;
                    unfocusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd)
                            - Math.max(candidateStart, prevSpanStart);
                }
            }
        }
        return (focusableWeakCandidate != null) ? focusableWeakCandidate : unfocusableWeakCandidate;
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return mPendingSavedState == null && !mPendingSpanCountChange;
    }

    @Override
    public int computeHorizontalScrollRange(RecyclerView.State state) {
        if (mUsingSpansToEstimateScrollBarDimensions) {
            return computeScrollRangeWithSpanInfo(state);
        } else {
            return super.computeHorizontalScrollRange(state);
        }
    }

    @Override
    public int computeVerticalScrollRange(RecyclerView.State state) {
        if (mUsingSpansToEstimateScrollBarDimensions) {
            return computeScrollRangeWithSpanInfo(state);
        } else {
            return super.computeVerticalScrollRange(state);
        }
    }

    @Override
    public int computeHorizontalScrollOffset(RecyclerView.State state) {
        if (mUsingSpansToEstimateScrollBarDimensions) {
            return computeScrollOffsetWithSpanInfo(state);
        } else {
            return super.computeHorizontalScrollOffset(state);
        }
    }

    @Override
    public int computeVerticalScrollOffset(RecyclerView.State state) {
        if (mUsingSpansToEstimateScrollBarDimensions) {
            return computeScrollOffsetWithSpanInfo(state);
        } else {
            return super.computeVerticalScrollOffset(state);
        }
    }

    /**
     * When this flag is set, the scroll offset and scroll range calculations will take account
     * of span information.
     *
     * <p>This is will increase the accuracy of the scroll bar's size and offset but will require
     * more calls to {@link SpanSizeLookup#getSpanGroupIndex(int, int)}".
     *
     * <p>This additional accuracy may or may not be needed, depending on the characteristics of
     * your layout.  You will likely benefit from this accuracy when:
     *
     * <ul>
     *   <li>The variation in item span sizes is large.
     *   <li>The size of your data set is small (if your data set is large, the scrollbar will
     *   likely be very small anyway, and thus the increased accuracy has less impact).
     *   <li>Calls to {@link SpanSizeLookup#getSpanGroupIndex(int, int)} are fast.
     * </ul>
     *
     * <p>If you decide to enable this feature, you should be sure that calls to
     * {@link SpanSizeLookup#getSpanGroupIndex(int, int)} are fast, that set span group index
     * caching is set to true via a call to
     * {@link SpanSizeLookup#setSpanGroupIndexCacheEnabled(boolean),
     * and span index caching is also enabled via a call to
     * {@link SpanSizeLookup#setSpanIndexCacheEnabled(boolean)}}.
     */
    public void setUsingSpansToEstimateScrollbarDimensions(
            boolean useSpansToEstimateScrollBarDimensions) {
        mUsingSpansToEstimateScrollBarDimensions = useSpansToEstimateScrollBarDimensions;
    }

    /**
     * Returns true if the scroll offset and scroll range calculations take account of span
     * information. See {@link #setUsingSpansToEstimateScrollbarDimensions(boolean)} for more
     * information on this topic. Defaults to {@code false}.
     *
     * @return true if the scroll offset and scroll range calculations take account of span
     * information.
     */
    public boolean isUsingSpansToEstimateScrollbarDimensions() {
        return mUsingSpansToEstimateScrollBarDimensions;
    }

    private int computeScrollRangeWithSpanInfo(RecyclerView.State state) {
        if (getChildCount() == 0 || state.getItemCount() == 0) {
            return 0;
        }
        ensureLayoutState();

        View startChild = findFirstVisibleChildClosestToStart(!isSmoothScrollbarEnabled(), true);
        View endChild = findFirstVisibleChildClosestToEnd(!isSmoothScrollbarEnabled(), true);

        if (startChild == null || endChild == null) {
            return 0;
        }
        if (!isSmoothScrollbarEnabled()) {
            return mSpanSizeLookup.getCachedSpanGroupIndex(
                    state.getItemCount() - 1, mSpanCount) + 1;
        }

        // smooth scrollbar enabled. try to estimate better.
        final int laidOutArea = mOrientationHelper.getDecoratedEnd(endChild)
                - mOrientationHelper.getDecoratedStart(startChild);

        final int firstVisibleSpan =
                mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild), mSpanCount);
        final int lastVisibleSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild),
                mSpanCount);
        final int totalSpans = mSpanSizeLookup.getCachedSpanGroupIndex(state.getItemCount() - 1,
                mSpanCount) + 1;
        final int laidOutSpans = lastVisibleSpan - firstVisibleSpan + 1;

        // estimate a size for full list.
        return (int) (((float) laidOutArea / laidOutSpans) * totalSpans);
    }

    private int computeScrollOffsetWithSpanInfo(RecyclerView.State state) {
        if (getChildCount() == 0 || state.getItemCount() == 0) {
            return 0;
        }
        ensureLayoutState();

        boolean smoothScrollEnabled = isSmoothScrollbarEnabled();
        View startChild = findFirstVisibleChildClosestToStart(!smoothScrollEnabled, true);
        View endChild = findFirstVisibleChildClosestToEnd(!smoothScrollEnabled, true);
        if (startChild == null || endChild == null) {
            return 0;
        }
        int startChildSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild),
                mSpanCount);
        int endChildSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild),
                mSpanCount);

        final int minSpan = Math.min(startChildSpan, endChildSpan);
        final int maxSpan = Math.max(startChildSpan, endChildSpan);
        final int totalSpans = mSpanSizeLookup.getCachedSpanGroupIndex(state.getItemCount() - 1,
                mSpanCount) + 1;

        final int spansBefore = mShouldReverseLayout
                ? Math.max(0, totalSpans - maxSpan - 1)
                : Math.max(0, minSpan);
        if (!smoothScrollEnabled) {
            return spansBefore;
        }
        final int laidOutArea = Math.abs(mOrientationHelper.getDecoratedEnd(endChild)
                - mOrientationHelper.getDecoratedStart(startChild));

        final int firstVisibleSpan =
                mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild), mSpanCount);
        final int lastVisibleSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild),
                mSpanCount);
        final int laidOutSpans = lastVisibleSpan - firstVisibleSpan + 1;
        final float avgSizePerSpan = (float) laidOutArea / laidOutSpans;

        return Math.round(spansBefore * avgSizePerSpan + (mOrientationHelper.getStartAfterPadding()
            - mOrientationHelper.getDecoratedStart(startChild)));
    }

    /**
     * Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
     */
    public static final class DefaultSpanSizeLookup extends SpanSizeLookup {

        @Override
        public int getSpanSize(int position) {
            return 1;
        }

        @Override
        public int getSpanIndex(int position, int spanCount) {
            return position % spanCount;
        }
    }

    /**
     * LayoutParams used by GridLayoutManager.
     * <p>
     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
     * expected to fill all of the space given to it.
     */
    public static class LayoutParams extends RecyclerView.LayoutParams {

        /**
         * Span Id for Views that are not laid out yet.
         */
        public static final int INVALID_SPAN_ID = -1;

        int mSpanIndex = INVALID_SPAN_ID;

        int mSpanSize = 0;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(RecyclerView.LayoutParams source) {
            super(source);
        }

        /**
         * Returns the current span index of this View. If the View is not laid out yet, the return
         * value is <code>undefined</code>.
         * <p>
         * Starting with RecyclerView <b>24.2.0</b>, span indices are always indexed from position 0
         * even if the layout is RTL. In a vertical GridLayoutManager, <b>leftmost</b> span is span
         * 0 if the layout is <b>LTR</b> and <b>rightmost</b> span is span 0 if the layout is
         * <b>RTL</b>. Prior to 24.2.0, it was the opposite which was conflicting with
         * {@link SpanSizeLookup#getSpanIndex(int, int)}.
         * <p>
         * If the View occupies multiple spans, span with the minimum index is returned.
         *
         * @return The span index of the View.
         */
        public int getSpanIndex() {
            return mSpanIndex;
        }

        /**
         * Returns the number of spans occupied by this View. If the View not laid out yet, the
         * return value is <code>undefined</code>.
         *
         * @return The number of spans occupied by this View.
         */
        public int getSpanSize() {
            return mSpanSize;
        }
    }

}