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