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 languag`e 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      * The measure spec for the scroll direction.
42      */
43     static final int MAIN_DIR_SPEC =
44             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
45     /**
46      * Span size have been changed but we've not done a new layout calculation.
47      */
48     boolean mPendingSpanCountChange = false;
49     int mSpanCount = DEFAULT_SPAN_COUNT;
50     /**
51      * Right borders for each span.
52      * <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
53      * and end is {@link #mCachedBorders}[i].
54      */
55     int [] mCachedBorders;
56     /**
57      * Temporary array to keep views in layoutChunk method
58      */
59     View[] mSet;
60     final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
61     final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
62     SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
63     // re-used variable to acquire decor insets from RecyclerView
64     final Rect mDecorInsets = new Rect();
65 
66 
67     /**
68      * Constructor used when layout manager is set in XML by RecyclerView attribute
69      * "layoutManager". If spanCount is not specified in the XML, it defaults to a
70      * single column.
71      *
72      * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount
73      */
GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)74     public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
75                              int defStyleRes) {
76         super(context, attrs, defStyleAttr, defStyleRes);
77         Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
78         setSpanCount(properties.spanCount);
79     }
80 
81     /**
82      * Creates a vertical GridLayoutManager
83      *
84      * @param context Current context, will be used to access resources.
85      * @param spanCount The number of columns in the grid
86      */
GridLayoutManager(Context context, int spanCount)87     public GridLayoutManager(Context context, int spanCount) {
88         super(context);
89         setSpanCount(spanCount);
90     }
91 
92     /**
93      * @param context Current context, will be used to access resources.
94      * @param spanCount The number of columns or rows in the grid
95      * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
96      *                      #VERTICAL}.
97      * @param reverseLayout When set to true, layouts from end to start.
98      */
GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)99     public GridLayoutManager(Context context, int spanCount, int orientation,
100             boolean reverseLayout) {
101         super(context, orientation, reverseLayout);
102         setSpanCount(spanCount);
103     }
104 
105     /**
106      * stackFromEnd is not supported by GridLayoutManager. Consider using
107      * {@link #setReverseLayout(boolean)}.
108      */
109     @Override
setStackFromEnd(boolean stackFromEnd)110     public void setStackFromEnd(boolean stackFromEnd) {
111         if (stackFromEnd) {
112             throw new UnsupportedOperationException(
113                     "GridLayoutManager does not support stack from end."
114                             + " Consider using reverse layout");
115         }
116         super.setStackFromEnd(false);
117     }
118 
119     @Override
getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)120     public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
121             RecyclerView.State state) {
122         if (mOrientation == HORIZONTAL) {
123             return mSpanCount;
124         }
125         if (state.getItemCount() < 1) {
126             return 0;
127         }
128         return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
129     }
130 
131     @Override
getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)132     public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
133             RecyclerView.State state) {
134         if (mOrientation == VERTICAL) {
135             return mSpanCount;
136         }
137         if (state.getItemCount() < 1) {
138             return 0;
139         }
140         return getSpanGroupIndex(recycler, state, state.getItemCount() - 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         if (!state.isPreLayout()) {
177             mPendingSpanCountChange = false;
178         }
179     }
180 
clearPreLayoutSpanMappingCache()181     private void clearPreLayoutSpanMappingCache() {
182         mPreLayoutSpanSizeCache.clear();
183         mPreLayoutSpanIndexCache.clear();
184     }
185 
cachePreLayoutSpanMapping()186     private void cachePreLayoutSpanMapping() {
187         final int childCount = getChildCount();
188         for (int i = 0; i < childCount; i++) {
189             final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
190             final int viewPosition = lp.getViewLayoutPosition();
191             mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
192             mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
193         }
194     }
195 
196     @Override
onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)197     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
198         mSpanSizeLookup.invalidateSpanIndexCache();
199     }
200 
201     @Override
onItemsChanged(RecyclerView recyclerView)202     public void onItemsChanged(RecyclerView recyclerView) {
203         mSpanSizeLookup.invalidateSpanIndexCache();
204     }
205 
206     @Override
onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)207     public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
208         mSpanSizeLookup.invalidateSpanIndexCache();
209     }
210 
211     @Override
onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, Object payload)212     public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
213             Object payload) {
214         mSpanSizeLookup.invalidateSpanIndexCache();
215     }
216 
217     @Override
onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)218     public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
219         mSpanSizeLookup.invalidateSpanIndexCache();
220     }
221 
222     @Override
generateDefaultLayoutParams()223     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
224         return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
225                 ViewGroup.LayoutParams.WRAP_CONTENT);
226     }
227 
228     @Override
generateLayoutParams(Context c, AttributeSet attrs)229     public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
230         return new LayoutParams(c, attrs);
231     }
232 
233     @Override
generateLayoutParams(ViewGroup.LayoutParams lp)234     public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
235         if (lp instanceof ViewGroup.MarginLayoutParams) {
236             return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
237         } else {
238             return new LayoutParams(lp);
239         }
240     }
241 
242     @Override
checkLayoutParams(RecyclerView.LayoutParams lp)243     public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
244         return lp instanceof LayoutParams;
245     }
246 
247     /**
248      * Sets the source to get the number of spans occupied by each item in the adapter.
249      *
250      * @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
251      *                       occupied by each item
252      */
setSpanSizeLookup(SpanSizeLookup spanSizeLookup)253     public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
254         mSpanSizeLookup = spanSizeLookup;
255     }
256 
257     /**
258      * Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
259      *
260      * @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
261      */
getSpanSizeLookup()262     public SpanSizeLookup getSpanSizeLookup() {
263         return mSpanSizeLookup;
264     }
265 
updateMeasurements()266     private void updateMeasurements() {
267         int totalSpace;
268         if (getOrientation() == VERTICAL) {
269             totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
270         } else {
271             totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
272         }
273         calculateItemBorders(totalSpace);
274     }
275 
calculateItemBorders(int totalSpace)276     private void calculateItemBorders(int totalSpace) {
277         if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
278                 || mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
279             mCachedBorders = new int[mSpanCount + 1];
280         }
281         mCachedBorders[0] = 0;
282         int sizePerSpan = totalSpace / mSpanCount;
283         int sizePerSpanRemainder = totalSpace % mSpanCount;
284         int consumedPixels = 0;
285         int additionalSize = 0;
286         for (int i = 1; i <= mSpanCount; i++) {
287             int itemSize = sizePerSpan;
288             additionalSize += sizePerSpanRemainder;
289             if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
290                 itemSize += 1;
291                 additionalSize -= mSpanCount;
292             }
293             consumedPixels += itemSize;
294             mCachedBorders[i] = consumedPixels;
295         }
296     }
297 
298     @Override
onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo)299     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
300                        AnchorInfo anchorInfo) {
301         super.onAnchorReady(recycler, state, anchorInfo);
302         updateMeasurements();
303         if (state.getItemCount() > 0 && !state.isPreLayout()) {
304             ensureAnchorIsInFirstSpan(recycler, state, anchorInfo);
305         }
306         if (mSet == null || mSet.length != mSpanCount) {
307             mSet = new View[mSpanCount];
308         }
309     }
310 
ensureAnchorIsInFirstSpan(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo)311     private void ensureAnchorIsInFirstSpan(RecyclerView.Recycler recycler, RecyclerView.State state,
312                                            AnchorInfo anchorInfo) {
313         int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
314         while (span > 0 && anchorInfo.mPosition > 0) {
315             anchorInfo.mPosition--;
316             span = getSpanIndex(recycler, state, anchorInfo.mPosition);
317         }
318     }
319 
320     @Override
findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, int start, int end, int itemCount)321     View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
322                             int start, int end, int itemCount) {
323         ensureLayoutState();
324         View invalidMatch = null;
325         View outOfBoundsMatch = null;
326         final int boundsStart = mOrientationHelper.getStartAfterPadding();
327         final int boundsEnd = mOrientationHelper.getEndAfterPadding();
328         final int diff = end > start ? 1 : -1;
329         for (int i = start; i != end; i += diff) {
330             final View view = getChildAt(i);
331             final int position = getPosition(view);
332             if (position >= 0 && position < itemCount) {
333                 final int span = getSpanIndex(recycler, state, position);
334                 if (span != 0) {
335                     continue;
336                 }
337                 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
338                     if (invalidMatch == null) {
339                         invalidMatch = view; // removed item, least preferred
340                     }
341                 } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd ||
342                         mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
343                     if (outOfBoundsMatch == null) {
344                         outOfBoundsMatch = view; // item is not visible, less preferred
345                     }
346                 } else {
347                     return view;
348                 }
349             }
350         }
351         return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
352     }
353 
getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int viewPosition)354     private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
355             int viewPosition) {
356         if (!state.isPreLayout()) {
357             return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
358         }
359         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
360         if (adapterPosition == -1) {
361             if (DEBUG) {
362                 throw new RuntimeException("Cannot find span group index for position "
363                         + viewPosition);
364             }
365             Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
366             return 0;
367         }
368         return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
369     }
370 
getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)371     private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
372         if (!state.isPreLayout()) {
373             return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
374         }
375         final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
376         if (cached != -1) {
377             return cached;
378         }
379         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
380         if (adapterPosition == -1) {
381             if (DEBUG) {
382                 throw new RuntimeException("Cannot find span index for pre layout position. It is"
383                         + " not cached, not in the adapter. Pos:" + pos);
384             }
385             Log.w(TAG, "Cannot find span size for pre layout position. It is"
386                     + " not cached, not in the adapter. Pos:" + pos);
387             return 0;
388         }
389         return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
390     }
391 
getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos)392     private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
393         if (!state.isPreLayout()) {
394             return mSpanSizeLookup.getSpanSize(pos);
395         }
396         final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
397         if (cached != -1) {
398             return cached;
399         }
400         final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
401         if (adapterPosition == -1) {
402             if (DEBUG) {
403                 throw new RuntimeException("Cannot find span size for pre layout position. It is"
404                         + " not cached, not in the adapter. Pos:" + pos);
405             }
406             Log.w(TAG, "Cannot find span size for pre layout position. It is"
407                     + " not cached, not in the adapter. Pos:" + pos);
408             return 1;
409         }
410         return mSpanSizeLookup.getSpanSize(adapterPosition);
411     }
412 
413     @Override
layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result)414     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
415             LayoutState layoutState, LayoutChunkResult result) {
416         final boolean layingOutInPrimaryDirection =
417                 layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
418         int count = 0;
419         int consumedSpanCount = 0;
420         int remainingSpan = mSpanCount;
421         if (!layingOutInPrimaryDirection) {
422             int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
423             int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
424             remainingSpan = itemSpanIndex + itemSpanSize;
425         }
426         while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
427             int pos = layoutState.mCurrentPosition;
428             final int spanSize = getSpanSize(recycler, state, pos);
429             if (spanSize > mSpanCount) {
430                 throw new IllegalArgumentException("Item at position " + pos + " requires " +
431                         spanSize + " spans but GridLayoutManager has only " + mSpanCount
432                         + " spans.");
433             }
434             remainingSpan -= spanSize;
435             if (remainingSpan < 0) {
436                 break; // item did not fit into this row or column
437             }
438             View view = layoutState.next(recycler);
439             if (view == null) {
440                 break;
441             }
442             consumedSpanCount += spanSize;
443             mSet[count] = view;
444             count++;
445         }
446 
447         if (count == 0) {
448             result.mFinished = true;
449             return;
450         }
451 
452         int maxSize = 0;
453 
454         // we should assign spans before item decor offsets are calculated
455         assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
456         for (int i = 0; i < count; i++) {
457             View view = mSet[i];
458             if (layoutState.mScrapList == null) {
459                 if (layingOutInPrimaryDirection) {
460                     addView(view);
461                 } else {
462                     addView(view, 0);
463                 }
464             } else {
465                 if (layingOutInPrimaryDirection) {
466                     addDisappearingView(view);
467                 } else {
468                     addDisappearingView(view, 0);
469                 }
470             }
471 
472             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
473             final int spec = View.MeasureSpec.makeMeasureSpec(
474                     mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
475                             mCachedBorders[lp.mSpanIndex],
476                     View.MeasureSpec.EXACTLY);
477             if (mOrientation == VERTICAL) {
478                 measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height), false);
479             } else {
480                 measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec, false);
481             }
482             final int size = mOrientationHelper.getDecoratedMeasurement(view);
483             if (size > maxSize) {
484                 maxSize = size;
485             }
486         }
487 
488         // views that did not measure the maxSize has to be re-measured
489         final int maxMeasureSpec = getMainDirSpec(maxSize);
490         for (int i = 0; i < count; i ++) {
491             final View view = mSet[i];
492             if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
493                 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
494                 final int spec = View.MeasureSpec.makeMeasureSpec(
495                         mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
496                                 mCachedBorders[lp.mSpanIndex],
497                         View.MeasureSpec.EXACTLY);
498                 if (mOrientation == VERTICAL) {
499                     measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true);
500                 } else {
501                     measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true);
502                 }
503             }
504         }
505 
506         result.mConsumed = maxSize;
507 
508         int left = 0, right = 0, top = 0, bottom = 0;
509         if (mOrientation == VERTICAL) {
510             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
511                 bottom = layoutState.mOffset;
512                 top = bottom - maxSize;
513             } else {
514                 top = layoutState.mOffset;
515                 bottom = top + maxSize;
516             }
517         } else {
518             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
519                 right = layoutState.mOffset;
520                 left = right - maxSize;
521             } else {
522                 left = layoutState.mOffset;
523                 right = left + maxSize;
524             }
525         }
526         for (int i = 0; i < count; i++) {
527             View view = mSet[i];
528             LayoutParams params = (LayoutParams) view.getLayoutParams();
529             if (mOrientation == VERTICAL) {
530                 left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
531                 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
532             } else {
533                 top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
534                 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
535             }
536             // We calculate everything with View's bounding box (which includes decor and margins)
537             // To calculate correct layout position, we subtract margins.
538             layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
539                     right - params.rightMargin, bottom - params.bottomMargin);
540             if (DEBUG) {
541                 Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
542                         + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
543                         + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
544                         + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
545             }
546             // Consume the available space if the view is not removed OR changed
547             if (params.isItemRemoved() || params.isItemChanged()) {
548                 result.mIgnoreConsumed = true;
549             }
550             result.mFocusable |= view.isFocusable();
551         }
552         Arrays.fill(mSet, null);
553     }
554 
getMainDirSpec(int dim)555     private int getMainDirSpec(int dim) {
556         if (dim < 0) {
557             return MAIN_DIR_SPEC;
558         } else {
559             return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
560         }
561     }
562 
measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, boolean capBothSpecs)563     private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
564             boolean capBothSpecs) {
565         calculateItemDecorationsForChild(child, mDecorInsets);
566         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
567         if (capBothSpecs || mOrientation == VERTICAL) {
568             widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
569                     lp.rightMargin + mDecorInsets.right);
570         }
571         if (capBothSpecs || mOrientation == HORIZONTAL) {
572             heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
573                     lp.bottomMargin + mDecorInsets.bottom);
574         }
575         child.measure(widthSpec, heightSpec);
576     }
577 
updateSpecWithExtra(int spec, int startInset, int endInset)578     private int updateSpecWithExtra(int spec, int startInset, int endInset) {
579         if (startInset == 0 && endInset == 0) {
580             return spec;
581         }
582         final int mode = View.MeasureSpec.getMode(spec);
583         if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
584             return View.MeasureSpec.makeMeasureSpec(
585                     View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
586         }
587         return spec;
588     }
589 
assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, int consumedSpanCount, boolean layingOutInPrimaryDirection)590     private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
591             int consumedSpanCount, boolean layingOutInPrimaryDirection) {
592         int span, spanDiff, start, end, diff;
593         // make sure we traverse from min position to max position
594         if (layingOutInPrimaryDirection) {
595             start = 0;
596             end = count;
597             diff = 1;
598         } else {
599             start = count - 1;
600             end = -1;
601             diff = -1;
602         }
603         if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
604             span = mSpanCount - 1;
605             spanDiff = -1;
606         } else {
607             span = 0;
608             spanDiff = 1;
609         }
610         for (int i = start; i != end; i += diff) {
611             View view = mSet[i];
612             LayoutParams params = (LayoutParams) view.getLayoutParams();
613             params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
614             if (spanDiff == -1 && params.mSpanSize > 1) {
615                 params.mSpanIndex = span - (params.mSpanSize - 1);
616             } else {
617                 params.mSpanIndex = span;
618             }
619             span += spanDiff * params.mSpanSize;
620         }
621     }
622 
623     /**
624      * Returns the number of spans laid out by this grid.
625      *
626      * @return The number of spans
627      * @see #setSpanCount(int)
628      */
getSpanCount()629     public int getSpanCount() {
630         return mSpanCount;
631     }
632 
633     /**
634      * Sets the number of spans to be laid out.
635      * <p>
636      * If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
637      * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
638      *
639      * @param spanCount The total number of spans in the grid
640      * @see #getSpanCount()
641      */
setSpanCount(int spanCount)642     public void setSpanCount(int spanCount) {
643         if (spanCount == mSpanCount) {
644             return;
645         }
646         mPendingSpanCountChange = true;
647         if (spanCount < 1) {
648             throw new IllegalArgumentException("Span count should be at least 1. Provided "
649                     + spanCount);
650         }
651         mSpanCount = spanCount;
652         mSpanSizeLookup.invalidateSpanIndexCache();
653     }
654 
655     /**
656      * A helper class to provide the number of spans each item occupies.
657      * <p>
658      * Default implementation sets each item to occupy exactly 1 span.
659      *
660      * @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
661      */
662     public static abstract class SpanSizeLookup {
663 
664         final SparseIntArray mSpanIndexCache = new SparseIntArray();
665 
666         private boolean mCacheSpanIndices = false;
667 
668         /**
669          * Returns the number of span occupied by the item at <code>position</code>.
670          *
671          * @param position The adapter position of the item
672          * @return The number of spans occupied by the item at the provided position
673          */
getSpanSize(int position)674         abstract public int getSpanSize(int position);
675 
676         /**
677          * Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
678          * not. By default these values are not cached. If you are not overriding
679          * {@link #getSpanIndex(int, int)}, you should set this to true for better performance.
680          *
681          * @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
682          */
setSpanIndexCacheEnabled(boolean cacheSpanIndices)683         public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
684             mCacheSpanIndices = cacheSpanIndices;
685         }
686 
687         /**
688          * Clears the span index cache. GridLayoutManager automatically calls this method when
689          * adapter changes occur.
690          */
invalidateSpanIndexCache()691         public void invalidateSpanIndexCache() {
692             mSpanIndexCache.clear();
693         }
694 
695         /**
696          * Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
697          *
698          * @return True if results of {@link #getSpanIndex(int, int)} are cached.
699          */
isSpanIndexCacheEnabled()700         public boolean isSpanIndexCacheEnabled() {
701             return mCacheSpanIndices;
702         }
703 
getCachedSpanIndex(int position, int spanCount)704         int getCachedSpanIndex(int position, int spanCount) {
705             if (!mCacheSpanIndices) {
706                 return getSpanIndex(position, spanCount);
707             }
708             final int existing = mSpanIndexCache.get(position, -1);
709             if (existing != -1) {
710                 return existing;
711             }
712             final int value = getSpanIndex(position, spanCount);
713             mSpanIndexCache.put(position, value);
714             return value;
715         }
716 
717         /**
718          * Returns the final span index of the provided position.
719          * <p>
720          * If you have a faster way to calculate span index for your items, you should override
721          * this method. Otherwise, you should enable span index cache
722          * ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
723          * disabled, default implementation traverses all items from 0 to
724          * <code>position</code>. When caching is enabled, it calculates from the closest cached
725          * value before the <code>position</code>.
726          * <p>
727          * If you override this method, you need to make sure it is consistent with
728          * {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
729          * each item. It is called only for the reference item and rest of the items
730          * are assigned to spans based on the reference item. For example, you cannot assign a
731          * position to span 2 while span 1 is empty.
732          * <p>
733          * Note that span offsets always start with 0 and are not affected by RTL.
734          *
735          * @param position  The position of the item
736          * @param spanCount The total number of spans in the grid
737          * @return The final span position of the item. Should be between 0 (inclusive) and
738          * <code>spanCount</code>(exclusive)
739          */
getSpanIndex(int position, int spanCount)740         public int getSpanIndex(int position, int spanCount) {
741             int positionSpanSize = getSpanSize(position);
742             if (positionSpanSize == spanCount) {
743                 return 0; // quick return for full-span items
744             }
745             int span = 0;
746             int startPos = 0;
747             // If caching is enabled, try to jump
748             if (mCacheSpanIndices && mSpanIndexCache.size() > 0) {
749                 int prevKey = findReferenceIndexFromCache(position);
750                 if (prevKey >= 0) {
751                     span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
752                     startPos = prevKey + 1;
753                 }
754             }
755             for (int i = startPos; i < position; i++) {
756                 int size = getSpanSize(i);
757                 span += size;
758                 if (span == spanCount) {
759                     span = 0;
760                 } else if (span > spanCount) {
761                     // did not fit, moving to next row / column
762                     span = size;
763                 }
764             }
765             if (span + positionSpanSize <= spanCount) {
766                 return span;
767             }
768             return 0;
769         }
770 
findReferenceIndexFromCache(int position)771         int findReferenceIndexFromCache(int position) {
772             int lo = 0;
773             int hi = mSpanIndexCache.size() - 1;
774 
775             while (lo <= hi) {
776                 final int mid = (lo + hi) >>> 1;
777                 final int midVal = mSpanIndexCache.keyAt(mid);
778                 if (midVal < position) {
779                     lo = mid + 1;
780                 } else {
781                     hi = mid - 1;
782                 }
783             }
784             int index = lo - 1;
785             if (index >= 0 && index < mSpanIndexCache.size()) {
786                 return mSpanIndexCache.keyAt(index);
787             }
788             return -1;
789         }
790 
791         /**
792          * Returns the index of the group this position belongs.
793          * <p>
794          * For example, if grid has 3 columns and each item occupies 1 span, span group index
795          * for item 1 will be 0, item 5 will be 1.
796          *
797          * @param adapterPosition The position in adapter
798          * @param spanCount The total number of spans in the grid
799          * @return The index of the span group including the item at the given adapter position
800          */
getSpanGroupIndex(int adapterPosition, int spanCount)801         public int getSpanGroupIndex(int adapterPosition, int spanCount) {
802             int span = 0;
803             int group = 0;
804             int positionSpanSize = getSpanSize(adapterPosition);
805             for (int i = 0; i < adapterPosition; i++) {
806                 int size = getSpanSize(i);
807                 span += size;
808                 if (span == spanCount) {
809                     span = 0;
810                     group++;
811                 } else if (span > spanCount) {
812                     // did not fit, moving to next row / column
813                     span = size;
814                     group++;
815                 }
816             }
817             if (span + positionSpanSize > spanCount) {
818                 group++;
819             }
820             return group;
821         }
822     }
823 
824     @Override
supportsPredictiveItemAnimations()825     public boolean supportsPredictiveItemAnimations() {
826         return mPendingSavedState == null && !mPendingSpanCountChange;
827     }
828 
829     /**
830      * Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
831      */
832     public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
833 
834         @Override
getSpanSize(int position)835         public int getSpanSize(int position) {
836             return 1;
837         }
838 
839         @Override
getSpanIndex(int position, int spanCount)840         public int getSpanIndex(int position, int spanCount) {
841             return position % spanCount;
842         }
843     }
844 
845     /**
846      * LayoutParams used by GridLayoutManager.
847      * <p>
848      * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
849      * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
850      * expected to fill all of the space given to it.
851      */
852     public static class LayoutParams extends RecyclerView.LayoutParams {
853 
854         /**
855          * Span Id for Views that are not laid out yet.
856          */
857         public static final int INVALID_SPAN_ID = -1;
858 
859         private int mSpanIndex = INVALID_SPAN_ID;
860 
861         private int mSpanSize = 0;
862 
LayoutParams(Context c, AttributeSet attrs)863         public LayoutParams(Context c, AttributeSet attrs) {
864             super(c, attrs);
865         }
866 
LayoutParams(int width, int height)867         public LayoutParams(int width, int height) {
868             super(width, height);
869         }
870 
LayoutParams(ViewGroup.MarginLayoutParams source)871         public LayoutParams(ViewGroup.MarginLayoutParams source) {
872             super(source);
873         }
874 
LayoutParams(ViewGroup.LayoutParams source)875         public LayoutParams(ViewGroup.LayoutParams source) {
876             super(source);
877         }
878 
LayoutParams(RecyclerView.LayoutParams source)879         public LayoutParams(RecyclerView.LayoutParams source) {
880             super(source);
881         }
882 
883         /**
884          * Returns the current span index of this View. If the View is not laid out yet, the return
885          * value is <code>undefined</code>.
886          * <p>
887          * Note that span index may change by whether the RecyclerView is RTL or not. For
888          * example, if the number of spans is 3 and layout is RTL, the rightmost item will have
889          * span index of 2. If the layout changes back to LTR, span index for this view will be 0.
890          * If the item was occupying 2 spans, span indices would be 1 and 0 respectively.
891          * <p>
892          * If the View occupies multiple spans, span with the minimum index is returned.
893          *
894          * @return The span index of the View.
895          */
getSpanIndex()896         public int getSpanIndex() {
897             return mSpanIndex;
898         }
899 
900         /**
901          * Returns the number of spans occupied by this View. If the View not laid out yet, the
902          * return value is <code>undefined</code>.
903          *
904          * @return The number of spans occupied by this View.
905          */
getSpanSize()906         public int getSpanSize() {
907             return mSpanSize;
908         }
909     }
910 
911 }
912