Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package androidx.recyclerview.widget;
     17 
     18 import android.content.Context;
     19 import android.graphics.Rect;
     20 import android.util.AttributeSet;
     21 import android.util.Log;
     22 import android.util.SparseIntArray;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 
     26 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
     27 
     28 import java.util.Arrays;
     29 
     30 /**
     31  * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
     32  * <p>
     33  * By default, each item occupies 1 span. You can change it by providing a custom
     34  * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
     35  */
     36 public class GridLayoutManager extends LinearLayoutManager {
     37 
     38     private static final boolean DEBUG = false;
     39     private static final String TAG = "GridLayoutManager";
     40     public static final int DEFAULT_SPAN_COUNT = -1;
     41     /**
     42      * Span size have been changed but we've not done a new layout calculation.
     43      */
     44     boolean mPendingSpanCountChange = false;
     45     int mSpanCount = DEFAULT_SPAN_COUNT;
     46     /**
     47      * Right borders for each span.
     48      * <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
     49      * and end is {@link #mCachedBorders}[i].
     50      */
     51     int [] mCachedBorders;
     52     /**
     53      * Temporary array to keep views in layoutChunk method
     54      */
     55     View[] mSet;
     56     final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
     57     final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
     58     SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
     59     // re-used variable to acquire decor insets from RecyclerView
     60     final Rect mDecorInsets = new Rect();
     61 
     62 
     63     /**
     64      * Constructor used when layout manager is set in XML by RecyclerView attribute
     65      * "layoutManager". If spanCount is not specified in the XML, it defaults to a
     66      * single column.
     67      *
     68      * @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount
     69      */
     70     public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
     71                              int defStyleRes) {
     72         super(context, attrs, defStyleAttr, defStyleRes);
     73         Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
     74         setSpanCount(properties.spanCount);
     75     }
     76 
     77     /**
     78      * Creates a vertical GridLayoutManager
     79      *
     80      * @param context Current context, will be used to access resources.
     81      * @param spanCount The number of columns in the grid
     82      */
     83     public GridLayoutManager(Context context, int spanCount) {
     84         super(context);
     85         setSpanCount(spanCount);
     86     }
     87 
     88     /**
     89      * @param context Current context, will be used to access resources.
     90      * @param spanCount The number of columns or rows in the grid
     91      * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
     92      *                      #VERTICAL}.
     93      * @param reverseLayout When set to true, layouts from end to start.
     94      */
     95     public GridLayoutManager(Context context, int spanCount,
     96             @RecyclerView.Orientation int orientation, boolean reverseLayout) {
     97         super(context, orientation, reverseLayout);
     98         setSpanCount(spanCount);
     99     }
    100 
    101     /**
    102      * stackFromEnd is not supported by GridLayoutManager. Consider using
    103      * {@link #setReverseLayout(boolean)}.
    104      */
    105     @Override
    106     public void setStackFromEnd(boolean stackFromEnd) {
    107         if (stackFromEnd) {
    108             throw new UnsupportedOperationException(
    109                     "GridLayoutManager does not support stack from end."
    110                             + " Consider using reverse layout");
    111         }
    112         super.setStackFromEnd(false);
    113     }
    114 
    115     @Override
    116     public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
    117             RecyclerView.State state) {
    118         if (mOrientation == HORIZONTAL) {
    119             return mSpanCount;
    120         }
    121         if (state.getItemCount() < 1) {
    122             return 0;
    123         }
    124 
    125         // Row count is one more than the last item's row index.
    126         return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
    127     }
    128 
    129     @Override
    130     public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
    131             RecyclerView.State state) {
    132         if (mOrientation == VERTICAL) {
    133             return mSpanCount;
    134         }
    135         if (state.getItemCount() < 1) {
    136             return 0;
    137         }
    138 
    139         // Column count is one more than the last item's column index.
    140         return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
    141     }
    142 
    143     @Override
    144     public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
    145             RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
    146         ViewGroup.LayoutParams lp = host.getLayoutParams();
    147         if (!(lp instanceof LayoutParams)) {
    148             super.onInitializeAccessibilityNodeInfoForItem(host, info);
    149             return;
    150         }
    151         LayoutParams glp = (LayoutParams) lp;
    152         int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
    153         if (mOrientation == HORIZONTAL) {
    154             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
    155                     glp.getSpanIndex(), glp.getSpanSize(),
    156                     spanGroupIndex, 1,
    157                     mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
    158         } else { // VERTICAL
    159             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
    160                     spanGroupIndex , 1,
    161                     glp.getSpanIndex(), glp.getSpanSize(),
    162                     mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
    163         }
    164     }
    165 
    166     @Override
    167     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    168         if (state.isPreLayout()) {
    169             cachePreLayoutSpanMapping();
    170         }
    171         super.onLayoutChildren(recycler, state);
    172         if (DEBUG) {
    173             validateChildOrder();
    174         }
    175         clearPreLayoutSpanMappingCache();
    176     }
    177 
    178     @Override
    179     public void onLayoutCompleted(RecyclerView.State state) {
    180         super.onLayoutCompleted(state);
    181         mPendingSpanCountChange = false;
    182     }
    183 
    184     private void clearPreLayoutSpanMappingCache() {
    185         mPreLayoutSpanSizeCache.clear();
    186         mPreLayoutSpanIndexCache.clear();
    187     }
    188 
    189     private void cachePreLayoutSpanMapping() {
    190         final int childCount = getChildCount();
    191         for (int i = 0; i < childCount; i++) {
    192             final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
    193             final int viewPosition = lp.getViewLayoutPosition();
    194             mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
    195             mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
    196         }
    197     }
    198 
    199     @Override
    200     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
    201         mSpanSizeLookup.invalidateSpanIndexCache();
    202     }
    203 
    204     @Override
    205     public void onItemsChanged(RecyclerView recyclerView) {
    206         mSpanSizeLookup.invalidateSpanIndexCache();
    207     }
    208 
    209     @Override
    210     public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
    211         mSpanSizeLookup.invalidateSpanIndexCache();
    212     }
    213 
    214     @Override
    215     public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
    216             Object payload) {
    217         mSpanSizeLookup.invalidateSpanIndexCache();
    218     }
    219 
    220     @Override
    221     public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
    222         mSpanSizeLookup.invalidateSpanIndexCache();
    223     }
    224 
    225     @Override
    226     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    227         if (mOrientation == HORIZONTAL) {
    228             return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
    229                     ViewGroup.LayoutParams.MATCH_PARENT);
    230         } else {
    231             return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    232                     ViewGroup.LayoutParams.WRAP_CONTENT);
    233         }
    234     }
    235 
    236     @Override
    237     public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
    238         return new LayoutParams(c, attrs);
    239     }
    240 
    241     @Override
    242     public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
    243         if (lp instanceof ViewGroup.MarginLayoutParams) {
    244             return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
    245         } else {
    246             return new LayoutParams(lp);
    247         }
    248     }
    249 
    250     @Override
    251     public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
    252         return lp instanceof LayoutParams;
    253     }
    254 
    255     /**
    256      * Sets the source to get the number of spans occupied by each item in the adapter.
    257      *
    258      * @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
    259      *                       occupied by each item
    260      */
    261     public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
    262         mSpanSizeLookup = spanSizeLookup;
    263     }
    264 
    265     /**
    266      * Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
    267      *
    268      * @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
    269      */
    270     public SpanSizeLookup getSpanSizeLookup() {
    271         return mSpanSizeLookup;
    272     }
    273 
    274     private void updateMeasurements() {
    275         int totalSpace;
    276         if (getOrientation() == VERTICAL) {
    277             totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
    278         } else {
    279             totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
    280         }
    281         calculateItemBorders(totalSpace);
    282     }
    283 
    284     @Override
    285     public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
    286         if (mCachedBorders == null) {
    287             super.setMeasuredDimension(childrenBounds, wSpec, hSpec);
    288         }
    289         final int width, height;
    290         final int horizontalPadding = getPaddingLeft() + getPaddingRight();
    291         final int verticalPadding = getPaddingTop() + getPaddingBottom();
    292         if (mOrientation == VERTICAL) {
    293             final int usedHeight = childrenBounds.height() + verticalPadding;
    294             height = chooseSize(hSpec, usedHeight, getMinimumHeight());
    295             width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding,
    296                     getMinimumWidth());
    297         } else {
    298             final int usedWidth = childrenBounds.width() + horizontalPadding;
    299             width = chooseSize(wSpec, usedWidth, getMinimumWidth());
    300             height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding,
    301                     getMinimumHeight());
    302         }
    303         setMeasuredDimension(width, height);
    304     }
    305 
    306     /**
    307      * @param totalSpace Total available space after padding is removed
    308      */
    309     private void calculateItemBorders(int totalSpace) {
    310         mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace);
    311     }
    312 
    313     /**
    314      * @param cachedBorders The out array
    315      * @param spanCount number of spans
    316      * @param totalSpace total available space after padding is removed
    317      * @return The updated array. Might be the same instance as the provided array if its size
    318      * has not changed.
    319      */
    320     static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
    321         if (cachedBorders == null || cachedBorders.length != spanCount + 1
    322                 || cachedBorders[cachedBorders.length - 1] != totalSpace) {
    323             cachedBorders = new int[spanCount + 1];
    324         }
    325         cachedBorders[0] = 0;
    326         int sizePerSpan = totalSpace / spanCount;
    327         int sizePerSpanRemainder = totalSpace % spanCount;
    328         int consumedPixels = 0;
    329         int additionalSize = 0;
    330         for (int i = 1; i <= spanCount; i++) {
    331             int itemSize = sizePerSpan;
    332             additionalSize += sizePerSpanRemainder;
    333             if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {
    334                 itemSize += 1;
    335                 additionalSize -= spanCount;
    336             }
    337             consumedPixels += itemSize;
    338             cachedBorders[i] = consumedPixels;
    339         }
    340         return cachedBorders;
    341     }
    342 
    343     int getSpaceForSpanRange(int startSpan, int spanSize) {
    344         if (mOrientation == VERTICAL && isLayoutRTL()) {
    345             return mCachedBorders[mSpanCount - startSpan]
    346                     - mCachedBorders[mSpanCount - startSpan - spanSize];
    347         } else {
    348             return mCachedBorders[startSpan + spanSize] - mCachedBorders[startSpan];
    349         }
    350     }
    351 
    352     @Override
    353     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
    354                        AnchorInfo anchorInfo, int itemDirection) {
    355         super.onAnchorReady(recycler, state, anchorInfo, itemDirection);
    356         updateMeasurements();
    357         if (state.getItemCount() > 0 && !state.isPreLayout()) {
    358             ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection);
    359         }
    360         ensureViewSet();
    361     }
    362 
    363     private void ensureViewSet() {
    364         if (mSet == null || mSet.length != mSpanCount) {
    365             mSet = new View[mSpanCount];
    366         }
    367     }
    368 
    369     @Override
    370     public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
    371             RecyclerView.State state) {
    372         updateMeasurements();
    373         ensureViewSet();
    374         return super.scrollHorizontallyBy(dx, recycler, state);
    375     }
    376 
    377     @Override
    378     public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    379             RecyclerView.State state) {
    380         updateMeasurements();
    381         ensureViewSet();
    382         return super.scrollVerticallyBy(dy, recycler, state);
    383     }
    384 
    385     private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler,
    386             RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
    387         final boolean layingOutInPrimaryDirection =
    388                 itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
    389         int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
    390         if (layingOutInPrimaryDirection) {
    391             // choose span 0
    392             while (span > 0 && anchorInfo.mPosition > 0) {
    393                 anchorInfo.mPosition--;
    394                 span = getSpanIndex(recycler, state, anchorInfo.mPosition);
    395             }
    396         } else {
    397             // choose the max span we can get. hopefully last one
    398             final int indexLimit = state.getItemCount() - 1;
    399             int pos = anchorInfo.mPosition;
    400             int bestSpan = span;
    401             while (pos < indexLimit) {
    402                 int next = getSpanIndex(recycler, state, pos + 1);
    403                 if (next > bestSpan) {
    404                     pos += 1;
    405                     bestSpan = next;
    406                 } else {
    407                     break;
    408                 }
    409             }
    410             anchorInfo.mPosition = pos;
    411         }
    412     }
    413 
    414     @Override
    415     View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
    416                             int start, int end, int itemCount) {
    417         ensureLayoutState();
    418         View invalidMatch = null;
    419         View outOfBoundsMatch = null;
    420         final int boundsStart = mOrientationHelper.getStartAfterPadding();
    421         final int boundsEnd = mOrientationHelper.getEndAfterPadding();
    422         final int diff = end > start ? 1 : -1;
    423 
    424         for (int i = start; i != end; i += diff) {
    425             final View view = getChildAt(i);
    426             final int position = getPosition(view);
    427             if (position >= 0 && position < itemCount) {
    428                 final int span = getSpanIndex(recycler, state, position);
    429                 if (span != 0) {
    430                     continue;
    431                 }
    432                 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
    433                     if (invalidMatch == null) {
    434                         invalidMatch = view; // removed item, least preferred
    435                     }
    436                 } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd
    437                         || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
    438                     if (outOfBoundsMatch == null) {
    439                         outOfBoundsMatch = view; // item is not visible, less preferred
    440                     }
    441                 } else {
    442                     return view;
    443                 }
    444             }
    445         }
    446         return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
    447     }
    448 
    449     private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
    450             int viewPosition) {
    451         if (!state.isPreLayout()) {
    452             return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
    453         }
    454         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
    455         if (adapterPosition == -1) {
    456             if (DEBUG) {
    457                 throw new RuntimeException("Cannot find span group index for position "
    458                         + viewPosition);
    459             }
    460             Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
    461             return 0;
    462         }
    463         return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
    464     }
    465 
    466     private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
    467         if (!state.isPreLayout()) {
    468             return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
    469         }
    470         final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
    471         if (cached != -1) {
    472             return cached;
    473         }
    474         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
    475         if (adapterPosition == -1) {
    476             if (DEBUG) {
    477                 throw new RuntimeException("Cannot find span index for pre layout position. It is"
    478                         + " not cached, not in the adapter. Pos:" + pos);
    479             }
    480             Log.w(TAG, "Cannot find span size for pre layout position. It is"
    481                     + " not cached, not in the adapter. Pos:" + pos);
    482             return 0;
    483         }
    484         return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
    485     }
    486 
    487     private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
    488         if (!state.isPreLayout()) {
    489             return mSpanSizeLookup.getSpanSize(pos);
    490         }
    491         final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
    492         if (cached != -1) {
    493             return cached;
    494         }
    495         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
    496         if (adapterPosition == -1) {
    497             if (DEBUG) {
    498                 throw new RuntimeException("Cannot find span size for pre layout position. It is"
    499                         + " not cached, not in the adapter. Pos:" + pos);
    500             }
    501             Log.w(TAG, "Cannot find span size for pre layout position. It is"
    502                     + " not cached, not in the adapter. Pos:" + pos);
    503             return 1;
    504         }
    505         return mSpanSizeLookup.getSpanSize(adapterPosition);
    506     }
    507 
    508     @Override
    509     void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
    510             LayoutPrefetchRegistry layoutPrefetchRegistry) {
    511         int remainingSpan = mSpanCount;
    512         int count = 0;
    513         while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
    514             final int pos = layoutState.mCurrentPosition;
    515             layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
    516             final int spanSize = mSpanSizeLookup.getSpanSize(pos);
    517             remainingSpan -= spanSize;
    518             layoutState.mCurrentPosition += layoutState.mItemDirection;
    519             count++;
    520         }
    521     }
    522 
    523     @Override
    524     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
    525             LayoutState layoutState, LayoutChunkResult result) {
    526         final int otherDirSpecMode = mOrientationHelper.getModeInOther();
    527         final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
    528         final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0;
    529         // if grid layout's dimensions are not specified, let the new row change the measurements
    530         // This is not perfect since we not covering all rows but still solves an important case
    531         // where they may have a header row which should be laid out according to children.
    532         if (flexibleInOtherDir) {
    533             updateMeasurements(); //  reset measurements
    534         }
    535         final boolean layingOutInPrimaryDirection =
    536                 layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
    537         int count = 0;
    538         int consumedSpanCount = 0;
    539         int remainingSpan = mSpanCount;
    540         if (!layingOutInPrimaryDirection) {
    541             int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
    542             int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
    543             remainingSpan = itemSpanIndex + itemSpanSize;
    544         }
    545         while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
    546             int pos = layoutState.mCurrentPosition;
    547             final int spanSize = getSpanSize(recycler, state, pos);
    548             if (spanSize > mSpanCount) {
    549                 throw new IllegalArgumentException("Item at position " + pos + " requires "
    550                         + spanSize + " spans but GridLayoutManager has only " + mSpanCount
    551                         + " spans.");
    552             }
    553             remainingSpan -= spanSize;
    554             if (remainingSpan < 0) {
    555                 break; // item did not fit into this row or column
    556             }
    557             View view = layoutState.next(recycler);
    558             if (view == null) {
    559                 break;
    560             }
    561             consumedSpanCount += spanSize;
    562             mSet[count] = view;
    563             count++;
    564         }
    565 
    566         if (count == 0) {
    567             result.mFinished = true;
    568             return;
    569         }
    570 
    571         int maxSize = 0;
    572         float maxSizeInOther = 0; // use a float to get size per span
    573 
    574         // we should assign spans before item decor offsets are calculated
    575         assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
    576         for (int i = 0; i < count; i++) {
    577             View view = mSet[i];
    578             if (layoutState.mScrapList == null) {
    579                 if (layingOutInPrimaryDirection) {
    580                     addView(view);
    581                 } else {
    582                     addView(view, 0);
    583                 }
    584             } else {
    585                 if (layingOutInPrimaryDirection) {
    586                     addDisappearingView(view);
    587                 } else {
    588                     addDisappearingView(view, 0);
    589                 }
    590             }
    591             calculateItemDecorationsForChild(view, mDecorInsets);
    592 
    593             measureChild(view, otherDirSpecMode, false);
    594             final int size = mOrientationHelper.getDecoratedMeasurement(view);
    595             if (size > maxSize) {
    596                 maxSize = size;
    597             }
    598             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
    599             final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view)
    600                     / lp.mSpanSize;
    601             if (otherSize > maxSizeInOther) {
    602                 maxSizeInOther = otherSize;
    603             }
    604         }
    605         if (flexibleInOtherDir) {
    606             // re-distribute columns
    607             guessMeasurement(maxSizeInOther, currentOtherDirSize);
    608             // now we should re-measure any item that was match parent.
    609             maxSize = 0;
    610             for (int i = 0; i < count; i++) {
    611                 View view = mSet[i];
    612                 measureChild(view, View.MeasureSpec.EXACTLY, true);
    613                 final int size = mOrientationHelper.getDecoratedMeasurement(view);
    614                 if (size > maxSize) {
    615                     maxSize = size;
    616                 }
    617             }
    618         }
    619 
    620         // Views that did not measure the maxSize has to be re-measured
    621         // We will stop doing this once we introduce Gravity in the GLM layout params
    622         for (int i = 0; i < count; i++) {
    623             final View view = mSet[i];
    624             if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
    625                 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
    626                 final Rect decorInsets = lp.mDecorInsets;
    627                 final int verticalInsets = decorInsets.top + decorInsets.bottom
    628                         + lp.topMargin + lp.bottomMargin;
    629                 final int horizontalInsets = decorInsets.left + decorInsets.right
    630                         + lp.leftMargin + lp.rightMargin;
    631                 final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
    632                 final int wSpec;
    633                 final int hSpec;
    634                 if (mOrientation == VERTICAL) {
    635                     wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
    636                             horizontalInsets, lp.width, false);
    637                     hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
    638                             View.MeasureSpec.EXACTLY);
    639                 } else {
    640                     wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets,
    641                             View.MeasureSpec.EXACTLY);
    642                     hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
    643                             verticalInsets, lp.height, false);
    644                 }
    645                 measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
    646             }
    647         }
    648 
    649         result.mConsumed = maxSize;
    650 
    651         int left = 0, right = 0, top = 0, bottom = 0;
    652         if (mOrientation == VERTICAL) {
    653             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
    654                 bottom = layoutState.mOffset;
    655                 top = bottom - maxSize;
    656             } else {
    657                 top = layoutState.mOffset;
    658                 bottom = top + maxSize;
    659             }
    660         } else {
    661             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
    662                 right = layoutState.mOffset;
    663                 left = right - maxSize;
    664             } else {
    665                 left = layoutState.mOffset;
    666                 right = left + maxSize;
    667             }
    668         }
    669         for (int i = 0; i < count; i++) {
    670             View view = mSet[i];
    671             LayoutParams params = (LayoutParams) view.getLayoutParams();
    672             if (mOrientation == VERTICAL) {
    673                 if (isLayoutRTL()) {
    674                     right = getPaddingLeft() + mCachedBorders[mSpanCount - params.mSpanIndex];
    675                     left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
    676                 } else {
    677                     left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
    678                     right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
    679                 }
    680             } else {
    681                 top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
    682                 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
    683             }
    684             // We calculate everything with View's bounding box (which includes decor and margins)
    685             // To calculate correct layout position, we subtract margins.
    686             layoutDecoratedWithMargins(view, left, top, right, bottom);
    687             if (DEBUG) {
    688                 Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
    689                         + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
    690                         + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
    691                         + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
    692             }
    693             // Consume the available space if the view is not removed OR changed
    694             if (params.isItemRemoved() || params.isItemChanged()) {
    695                 result.mIgnoreConsumed = true;
    696             }
    697             result.mFocusable |= view.hasFocusable();
    698         }
    699         Arrays.fill(mSet, null);
    700     }
    701 
    702     /**
    703      * Measures a child with currently known information. This is not necessarily the child's final
    704      * measurement. (see fillChunk for details).
    705      *
    706      * @param view The child view to be measured
    707      * @param otherDirParentSpecMode The RV measure spec that should be used in the secondary
    708      *                               orientation
    709      * @param alreadyMeasured True if we've already measured this view once
    710      */
    711     private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) {
    712         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
    713         final Rect decorInsets = lp.mDecorInsets;
    714         final int verticalInsets = decorInsets.top + decorInsets.bottom
    715                 + lp.topMargin + lp.bottomMargin;
    716         final int horizontalInsets = decorInsets.left + decorInsets.right
    717                 + lp.leftMargin + lp.rightMargin;
    718         final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
    719         final int wSpec;
    720         final int hSpec;
    721         if (mOrientation == VERTICAL) {
    722             wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
    723                     horizontalInsets, lp.width, false);
    724             hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),
    725                     verticalInsets, lp.height, true);
    726         } else {
    727             hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
    728                     verticalInsets, lp.height, false);
    729             wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(),
    730                     horizontalInsets, lp.width, true);
    731         }
    732         measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured);
    733     }
    734 
    735     /**
    736      * This is called after laying out a row (if vertical) or a column (if horizontal) when the
    737      * RecyclerView does not have exact measurement specs.
    738      * <p>
    739      * Here we try to assign a best guess width or height and re-do the layout to update other
    740      * views that wanted to MATCH_PARENT in the non-scroll orientation.
    741      *
    742      * @param maxSizeInOther The maximum size per span ratio from the measurement of the children.
    743      * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below.
    744      */
    745     private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) {
    746         final int contentSize = Math.round(maxSizeInOther * mSpanCount);
    747         // always re-calculate because borders were stretched during the fill
    748         calculateItemBorders(Math.max(contentSize, currentOtherDirSize));
    749     }
    750 
    751     private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
    752             boolean alreadyMeasured) {
    753         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
    754         final boolean measure;
    755         if (alreadyMeasured) {
    756             measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
    757         } else {
    758             measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
    759         }
    760         if (measure) {
    761             child.measure(widthSpec, heightSpec);
    762         }
    763     }
    764 
    765     private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
    766             int consumedSpanCount, boolean layingOutInPrimaryDirection) {
    767         // spans are always assigned from 0 to N no matter if it is RTL or not.
    768         // RTL is used only when positioning the view.
    769         int span, start, end, diff;
    770         // make sure we traverse from min position to max position
    771         if (layingOutInPrimaryDirection) {
    772             start = 0;
    773             end = count;
    774             diff = 1;
    775         } else {
    776             start = count - 1;
    777             end = -1;
    778             diff = -1;
    779         }
    780         span = 0;
    781         for (int i = start; i != end; i += diff) {
    782             View view = mSet[i];
    783             LayoutParams params = (LayoutParams) view.getLayoutParams();
    784             params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
    785             params.mSpanIndex = span;
    786             span += params.mSpanSize;
    787         }
    788     }
    789 
    790     /**
    791      * Returns the number of spans laid out by this grid.
    792      *
    793      * @return The number of spans
    794      * @see #setSpanCount(int)
    795      */
    796     public int getSpanCount() {
    797         return mSpanCount;
    798     }
    799 
    800     /**
    801      * Sets the number of spans to be laid out.
    802      * <p>
    803      * If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
    804      * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
    805      *
    806      * @param spanCount The total number of spans in the grid
    807      * @see #getSpanCount()
    808      */
    809     public void setSpanCount(int spanCount) {
    810         if (spanCount == mSpanCount) {
    811             return;
    812         }
    813         mPendingSpanCountChange = true;
    814         if (spanCount < 1) {
    815             throw new IllegalArgumentException("Span count should be at least 1. Provided "
    816                     + spanCount);
    817         }
    818         mSpanCount = spanCount;
    819         mSpanSizeLookup.invalidateSpanIndexCache();
    820         requestLayout();
    821     }
    822 
    823     /**
    824      * A helper class to provide the number of spans each item occupies.
    825      * <p>
    826      * Default implementation sets each item to occupy exactly 1 span.
    827      *
    828      * @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
    829      */
    830     public abstract static class SpanSizeLookup {
    831 
    832         final SparseIntArray mSpanIndexCache = new SparseIntArray();
    833 
    834         private boolean mCacheSpanIndices = false;
    835 
    836         /**
    837          * Returns the number of span occupied by the item at <code>position</code>.
    838          *
    839          * @param position The adapter position of the item
    840          * @return The number of spans occupied by the item at the provided position
    841          */
    842         public abstract int getSpanSize(int position);
    843 
    844         /**
    845          * Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
    846          * not. By default these values are not cached. If you are not overriding
    847          * {@link #getSpanIndex(int, int)}, you should set this to true for better performance.
    848          *
    849          * @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
    850          */
    851         public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
    852             mCacheSpanIndices = cacheSpanIndices;
    853         }
    854 
    855         /**
    856          * Clears the span index cache. GridLayoutManager automatically calls this method when
    857          * adapter changes occur.
    858          */
    859         public void invalidateSpanIndexCache() {
    860             mSpanIndexCache.clear();
    861         }
    862 
    863         /**
    864          * Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
    865          *
    866          * @return True if results of {@link #getSpanIndex(int, int)} are cached.
    867          */
    868         public boolean isSpanIndexCacheEnabled() {
    869             return mCacheSpanIndices;
    870         }
    871 
    872         int getCachedSpanIndex(int position, int spanCount) {
    873             if (!mCacheSpanIndices) {
    874                 return getSpanIndex(position, spanCount);
    875             }
    876             final int existing = mSpanIndexCache.get(position, -1);
    877             if (existing != -1) {
    878                 return existing;
    879             }
    880             final int value = getSpanIndex(position, spanCount);
    881             mSpanIndexCache.put(position, value);
    882             return value;
    883         }
    884 
    885         /**
    886          * Returns the final span index of the provided position.
    887          * <p>
    888          * If you have a faster way to calculate span index for your items, you should override
    889          * this method. Otherwise, you should enable span index cache
    890          * ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
    891          * disabled, default implementation traverses all items from 0 to
    892          * <code>position</code>. When caching is enabled, it calculates from the closest cached
    893          * value before the <code>position</code>.
    894          * <p>
    895          * If you override this method, you need to make sure it is consistent with
    896          * {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
    897          * each item. It is called only for the reference item and rest of the items
    898          * are assigned to spans based on the reference item. For example, you cannot assign a
    899          * position to span 2 while span 1 is empty.
    900          * <p>
    901          * Note that span offsets always start with 0 and are not affected by RTL.
    902          *
    903          * @param position  The position of the item
    904          * @param spanCount The total number of spans in the grid
    905          * @return The final span position of the item. Should be between 0 (inclusive) and
    906          * <code>spanCount</code>(exclusive)
    907          */
    908         public int getSpanIndex(int position, int spanCount) {
    909             int positionSpanSize = getSpanSize(position);
    910             if (positionSpanSize == spanCount) {
    911                 return 0; // quick return for full-span items
    912             }
    913             int span = 0;
    914             int startPos = 0;
    915             // If caching is enabled, try to jump
    916             if (mCacheSpanIndices && mSpanIndexCache.size() > 0) {
    917                 int prevKey = findReferenceIndexFromCache(position);
    918                 if (prevKey >= 0) {
    919                     span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
    920                     startPos = prevKey + 1;
    921                 }
    922             }
    923             for (int i = startPos; i < position; i++) {
    924                 int size = getSpanSize(i);
    925                 span += size;
    926                 if (span == spanCount) {
    927                     span = 0;
    928                 } else if (span > spanCount) {
    929                     // did not fit, moving to next row / column
    930                     span = size;
    931                 }
    932             }
    933             if (span + positionSpanSize <= spanCount) {
    934                 return span;
    935             }
    936             return 0;
    937         }
    938 
    939         int findReferenceIndexFromCache(int position) {
    940             int lo = 0;
    941             int hi = mSpanIndexCache.size() - 1;
    942 
    943             while (lo <= hi) {
    944                 final int mid = (lo + hi) >>> 1;
    945                 final int midVal = mSpanIndexCache.keyAt(mid);
    946                 if (midVal < position) {
    947                     lo = mid + 1;
    948                 } else {
    949                     hi = mid - 1;
    950                 }
    951             }
    952             int index = lo - 1;
    953             if (index >= 0 && index < mSpanIndexCache.size()) {
    954                 return mSpanIndexCache.keyAt(index);
    955             }
    956             return -1;
    957         }
    958 
    959         /**
    960          * Returns the index of the group this position belongs.
    961          * <p>
    962          * For example, if grid has 3 columns and each item occupies 1 span, span group index
    963          * for item 1 will be 0, item 5 will be 1.
    964          *
    965          * @param adapterPosition The position in adapter
    966          * @param spanCount The total number of spans in the grid
    967          * @return The index of the span group including the item at the given adapter position
    968          */
    969         public int getSpanGroupIndex(int adapterPosition, int spanCount) {
    970             int span = 0;
    971             int group = 0;
    972             int positionSpanSize = getSpanSize(adapterPosition);
    973             for (int i = 0; i < adapterPosition; i++) {
    974                 int size = getSpanSize(i);
    975                 span += size;
    976                 if (span == spanCount) {
    977                     span = 0;
    978                     group++;
    979                 } else if (span > spanCount) {
    980                     // did not fit, moving to next row / column
    981                     span = size;
    982                     group++;
    983                 }
    984             }
    985             if (span + positionSpanSize > spanCount) {
    986                 group++;
    987             }
    988             return group;
    989         }
    990     }
    991 
    992     @Override
    993     public View onFocusSearchFailed(View focused, int focusDirection,
    994             RecyclerView.Recycler recycler, RecyclerView.State state) {
    995         View prevFocusedChild = findContainingItemView(focused);
    996         if (prevFocusedChild == null) {
    997             return null;
    998         }
    999         LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams();
   1000         final int prevSpanStart = lp.mSpanIndex;
   1001         final int prevSpanEnd = lp.mSpanIndex + lp.mSpanSize;
   1002         View view = super.onFocusSearchFailed(focused, focusDirection, recycler, state);
   1003         if (view == null) {
   1004             return null;
   1005         }
   1006         // LinearLayoutManager finds the last child. What we want is the child which has the same
   1007         // spanIndex.
   1008         final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
   1009         final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout;
   1010         final int start, inc, limit;
   1011         if (ascend) {
   1012             start = getChildCount() - 1;
   1013             inc = -1;
   1014             limit = -1;
   1015         } else {
   1016             start = 0;
   1017             inc = 1;
   1018             limit = getChildCount();
   1019         }
   1020         final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL();
   1021 
   1022         // The focusable candidate to be picked if no perfect focusable candidate is found.
   1023         // The best focusable candidate is the one with the highest amount of span overlap with
   1024         // the currently focused view.
   1025         View focusableWeakCandidate = null; // somewhat matches but not strong
   1026         int focusableWeakCandidateSpanIndex = -1;
   1027         int focusableWeakCandidateOverlap = 0; // how many spans overlap
   1028 
   1029         // The unfocusable candidate to become visible on the screen next, if no perfect or
   1030         // weak focusable candidates are found to receive focus next.
   1031         // We are only interested in partially visible unfocusable views. These are views that are
   1032         // not fully visible, that is either partially overlapping, or out-of-bounds and right below
   1033         // or above RV's padded bounded area. The best unfocusable candidate is the one with the
   1034         // highest amount of span overlap with the currently focused view.
   1035         View unfocusableWeakCandidate = null; // somewhat matches but not strong
   1036         int unfocusableWeakCandidateSpanIndex = -1;
   1037         int unfocusableWeakCandidateOverlap = 0; // how many spans overlap
   1038 
   1039         // The span group index of the start child. This indicates the span group index of the
   1040         // next focusable item to receive focus, if a focusable item within the same span group
   1041         // exists. Any focusable item beyond this group index are not relevant since they
   1042         // were already stored in the layout before onFocusSearchFailed call and were not picked
   1043         // by the focusSearch algorithm.
   1044         int focusableSpanGroupIndex = getSpanGroupIndex(recycler, state, start);
   1045         for (int i = start; i != limit; i += inc) {
   1046             int spanGroupIndex = getSpanGroupIndex(recycler, state, i);
   1047             View candidate = getChildAt(i);
   1048             if (candidate == prevFocusedChild) {
   1049                 break;
   1050             }
   1051 
   1052             if (candidate.hasFocusable() && spanGroupIndex != focusableSpanGroupIndex) {
   1053                 // We are past the allowable span group index for the next focusable item.
   1054                 // The search only continues if no focusable weak candidates have been found up
   1055                 // until this point, in order to find the best unfocusable candidate to become
   1056                 // visible on the screen next.
   1057                 if (focusableWeakCandidate != null) {
   1058                     break;
   1059                 }
   1060                 continue;
   1061             }
   1062 
   1063             final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams();
   1064             final int candidateStart = candidateLp.mSpanIndex;
   1065             final int candidateEnd = candidateLp.mSpanIndex + candidateLp.mSpanSize;
   1066             if (candidate.hasFocusable() && candidateStart == prevSpanStart
   1067                     && candidateEnd == prevSpanEnd) {
   1068                 return candidate; // perfect match
   1069             }
   1070             boolean assignAsWeek = false;
   1071             if ((candidate.hasFocusable() && focusableWeakCandidate == null)
   1072                     || (!candidate.hasFocusable() && unfocusableWeakCandidate == null)) {
   1073                 assignAsWeek = true;
   1074             } else {
   1075                 int maxStart = Math.max(candidateStart, prevSpanStart);
   1076                 int minEnd = Math.min(candidateEnd, prevSpanEnd);
   1077                 int overlap = minEnd - maxStart;
   1078                 if (candidate.hasFocusable()) {
   1079                     if (overlap > focusableWeakCandidateOverlap) {
   1080                         assignAsWeek = true;
   1081                     } else if (overlap == focusableWeakCandidateOverlap
   1082                             && preferLastSpan == (candidateStart
   1083                             > focusableWeakCandidateSpanIndex)) {
   1084                         assignAsWeek = true;
   1085                     }
   1086                 } else if (focusableWeakCandidate == null
   1087                         && isViewPartiallyVisible(candidate, false, true)) {
   1088                     if (overlap > unfocusableWeakCandidateOverlap) {
   1089                         assignAsWeek = true;
   1090                     } else if (overlap == unfocusableWeakCandidateOverlap
   1091                             && preferLastSpan == (candidateStart
   1092                                     > unfocusableWeakCandidateSpanIndex)) {
   1093                         assignAsWeek = true;
   1094                     }
   1095                 }
   1096             }
   1097 
   1098             if (assignAsWeek) {
   1099                 if (candidate.hasFocusable()) {
   1100                     focusableWeakCandidate = candidate;
   1101                     focusableWeakCandidateSpanIndex = candidateLp.mSpanIndex;
   1102                     focusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd)
   1103                             - Math.max(candidateStart, prevSpanStart);
   1104                 } else {
   1105                     unfocusableWeakCandidate = candidate;
   1106                     unfocusableWeakCandidateSpanIndex = candidateLp.mSpanIndex;
   1107                     unfocusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd)
   1108                             - Math.max(candidateStart, prevSpanStart);
   1109                 }
   1110             }
   1111         }
   1112         return (focusableWeakCandidate != null) ? focusableWeakCandidate : unfocusableWeakCandidate;
   1113     }
   1114 
   1115     @Override
   1116     public boolean supportsPredictiveItemAnimations() {
   1117         return mPendingSavedState == null && !mPendingSpanCountChange;
   1118     }
   1119 
   1120     /**
   1121      * Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
   1122      */
   1123     public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
   1124 
   1125         @Override
   1126         public int getSpanSize(int position) {
   1127             return 1;
   1128         }
   1129 
   1130         @Override
   1131         public int getSpanIndex(int position, int spanCount) {
   1132             return position % spanCount;
   1133         }
   1134     }
   1135 
   1136     /**
   1137      * LayoutParams used by GridLayoutManager.
   1138      * <p>
   1139      * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
   1140      * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
   1141      * expected to fill all of the space given to it.
   1142      */
   1143     public static class LayoutParams extends RecyclerView.LayoutParams {
   1144 
   1145         /**
   1146          * Span Id for Views that are not laid out yet.
   1147          */
   1148         public static final int INVALID_SPAN_ID = -1;
   1149 
   1150         int mSpanIndex = INVALID_SPAN_ID;
   1151 
   1152         int mSpanSize = 0;
   1153 
   1154         public LayoutParams(Context c, AttributeSet attrs) {
   1155             super(c, attrs);
   1156         }
   1157 
   1158         public LayoutParams(int width, int height) {
   1159             super(width, height);
   1160         }
   1161 
   1162         public LayoutParams(ViewGroup.MarginLayoutParams source) {
   1163             super(source);
   1164         }
   1165 
   1166         public LayoutParams(ViewGroup.LayoutParams source) {
   1167             super(source);
   1168         }
   1169 
   1170         public LayoutParams(RecyclerView.LayoutParams source) {
   1171             super(source);
   1172         }
   1173 
   1174         /**
   1175          * Returns the current span index of this View. If the View is not laid out yet, the return
   1176          * value is <code>undefined</code>.
   1177          * <p>
   1178          * Starting with RecyclerView <b>24.2.0</b>, span indices are always indexed from position 0
   1179          * even if the layout is RTL. In a vertical GridLayoutManager, <b>leftmost</b> span is span
   1180          * 0 if the layout is <b>LTR</b> and <b>rightmost</b> span is span 0 if the layout is
   1181          * <b>RTL</b>. Prior to 24.2.0, it was the opposite which was conflicting with
   1182          * {@link SpanSizeLookup#getSpanIndex(int, int)}.
   1183          * <p>
   1184          * If the View occupies multiple spans, span with the minimum index is returned.
   1185          *
   1186          * @return The span index of the View.
   1187          */
   1188         public int getSpanIndex() {
   1189             return mSpanIndex;
   1190         }
   1191 
   1192         /**
   1193          * Returns the number of spans occupied by this View. If the View not laid out yet, the
   1194          * return value is <code>undefined</code>.
   1195          *
   1196          * @return The number of spans occupied by this View.
   1197          */
   1198         public int getSpanSize() {
   1199             return mSpanSize;
   1200         }
   1201     }
   1202 
   1203 }
   1204