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      */
GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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      */
GridLayoutManager(Context context, int spanCount)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      */
GridLayoutManager(Context context, int spanCount, @RecyclerView.Orientation int orientation, boolean reverseLayout)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
setStackFromEnd(boolean stackFromEnd)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
getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)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
getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)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
onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)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
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)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
onLayoutCompleted(RecyclerView.State state)179     public void onLayoutCompleted(RecyclerView.State state) {
180         super.onLayoutCompleted(state);
181         mPendingSpanCountChange = false;
182     }
183 
clearPreLayoutSpanMappingCache()184     private void clearPreLayoutSpanMappingCache() {
185         mPreLayoutSpanSizeCache.clear();
186         mPreLayoutSpanIndexCache.clear();
187     }
188 
cachePreLayoutSpanMapping()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
onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)200     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
201         mSpanSizeLookup.invalidateSpanIndexCache();
202     }
203 
204     @Override
onItemsChanged(RecyclerView recyclerView)205     public void onItemsChanged(RecyclerView recyclerView) {
206         mSpanSizeLookup.invalidateSpanIndexCache();
207     }
208 
209     @Override
onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)210     public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
211         mSpanSizeLookup.invalidateSpanIndexCache();
212     }
213 
214     @Override
onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, Object payload)215     public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
216             Object payload) {
217         mSpanSizeLookup.invalidateSpanIndexCache();
218     }
219 
220     @Override
onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)221     public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
222         mSpanSizeLookup.invalidateSpanIndexCache();
223     }
224 
225     @Override
generateDefaultLayoutParams()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
generateLayoutParams(Context c, AttributeSet attrs)237     public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
238         return new LayoutParams(c, attrs);
239     }
240 
241     @Override
generateLayoutParams(ViewGroup.LayoutParams lp)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
checkLayoutParams(RecyclerView.LayoutParams lp)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      */
setSpanSizeLookup(SpanSizeLookup spanSizeLookup)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      */
getSpanSizeLookup()270     public SpanSizeLookup getSpanSizeLookup() {
271         return mSpanSizeLookup;
272     }
273 
updateMeasurements()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
setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)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      */
calculateItemBorders(int totalSpace)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      */
calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace)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 
getSpaceForSpanRange(int startSpan, int spanSize)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
onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection)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 
ensureViewSet()363     private void ensureViewSet() {
364         if (mSet == null || mSet.length != mSpanCount) {
365             mSet = new View[mSpanCount];
366         }
367     }
368 
369     @Override
scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)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
scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)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 
ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection)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
findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, int start, int end, int itemCount)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 
getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int viewPosition)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 
getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)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 
getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)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
collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, LayoutPrefetchRegistry layoutPrefetchRegistry)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
layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result)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      */
measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured)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      */
guessMeasurement(float maxSizeInOther, int currentOtherDirSize)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 
measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, boolean alreadyMeasured)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 
assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, int consumedSpanCount, boolean layingOutInPrimaryDirection)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      */
getSpanCount()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      */
setSpanCount(int spanCount)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          */
getSpanSize(int position)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          */
setSpanIndexCacheEnabled(boolean cacheSpanIndices)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          */
invalidateSpanIndexCache()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          */
isSpanIndexCacheEnabled()868         public boolean isSpanIndexCacheEnabled() {
869             return mCacheSpanIndices;
870         }
871 
getCachedSpanIndex(int position, int spanCount)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          */
getSpanIndex(int position, int spanCount)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 
findReferenceIndexFromCache(int position)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          */
getSpanGroupIndex(int adapterPosition, int spanCount)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
onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state)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
supportsPredictiveItemAnimations()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
getSpanSize(int position)1126         public int getSpanSize(int position) {
1127             return 1;
1128         }
1129 
1130         @Override
getSpanIndex(int position, int spanCount)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 
LayoutParams(Context c, AttributeSet attrs)1154         public LayoutParams(Context c, AttributeSet attrs) {
1155             super(c, attrs);
1156         }
1157 
LayoutParams(int width, int height)1158         public LayoutParams(int width, int height) {
1159             super(width, height);
1160         }
1161 
LayoutParams(ViewGroup.MarginLayoutParams source)1162         public LayoutParams(ViewGroup.MarginLayoutParams source) {
1163             super(source);
1164         }
1165 
LayoutParams(ViewGroup.LayoutParams source)1166         public LayoutParams(ViewGroup.LayoutParams source) {
1167             super(source);
1168         }
1169 
LayoutParams(RecyclerView.LayoutParams source)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          */
getSpanIndex()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          */
getSpanSize()1198         public int getSpanSize() {
1199             return mSpanSize;
1200         }
1201     }
1202 
1203 }
1204