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