1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget;
18
19import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
20import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
21import static android.support.v7.widget.LayoutState.LAYOUT_END;
22import static android.support.v7.widget.LayoutState.LAYOUT_START;
23import static android.support.v7.widget.RecyclerView.NO_POSITION;
24
25import android.content.Context;
26import android.graphics.PointF;
27import android.graphics.Rect;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
32import android.support.v4.view.ViewCompat;
33import android.support.v4.view.accessibility.AccessibilityEventCompat;
34import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
35import android.support.v4.view.accessibility.AccessibilityRecordCompat;
36import android.util.AttributeSet;
37import android.util.Log;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.accessibility.AccessibilityEvent;
41
42import java.util.ArrayList;
43import java.util.Arrays;
44import java.util.BitSet;
45import java.util.List;
46
47/**
48 * A LayoutManager that lays out children in a staggered grid formation.
49 * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
50 * <p>
51 * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
52 * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
53 * control this behavior via {@link #setGapStrategy(int)}.
54 */
55public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
56
57    public static final String TAG = "StaggeredGridLayoutManager";
58
59    private static final boolean DEBUG = false;
60
61    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
62
63    public static final int VERTICAL = OrientationHelper.VERTICAL;
64
65    /**
66     * Does not do anything to hide gaps.
67     */
68    public static final int GAP_HANDLING_NONE = 0;
69
70    /**
71     * @deprecated No longer supported.
72     */
73    @SuppressWarnings("unused")
74    @Deprecated
75    public static final int GAP_HANDLING_LAZY = 1;
76
77    /**
78     * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
79     * check if there are gaps in the because of full span items. If it finds, it will re-layout
80     * and move items to correct positions with animations.
81     * <p>
82     * For example, if LayoutManager ends up with the following layout due to adapter changes:
83     * <pre>
84     * AAA
85     * _BC
86     * DDD
87     * </pre>
88     * <p>
89     * It will animate to the following state:
90     * <pre>
91     * AAA
92     * BC_
93     * DDD
94     * </pre>
95     */
96    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
97
98    private static final int INVALID_OFFSET = Integer.MIN_VALUE;
99    /**
100     * While trying to find next view to focus, LayoutManager will not try to scroll more
101     * than this factor times the total space of the list. If layout is vertical, total space is the
102     * height minus padding, if layout is horizontal, total space is the width minus padding.
103     */
104    private static final float MAX_SCROLL_FACTOR = 1 / 3f;
105
106    /**
107     * Number of spans
108     */
109    private int mSpanCount = -1;
110
111    private Span[] mSpans;
112
113    /**
114     * Primary orientation is the layout's orientation, secondary orientation is the orientation
115     * for spans. Having both makes code much cleaner for calculations.
116     */
117    @NonNull
118    OrientationHelper mPrimaryOrientation;
119    @NonNull
120    OrientationHelper mSecondaryOrientation;
121
122    private int mOrientation;
123
124    /**
125     * The width or height per span, depending on the orientation.
126     */
127    private int mSizePerSpan;
128
129    @NonNull
130    private final LayoutState mLayoutState;
131
132    private boolean mReverseLayout = false;
133
134    /**
135     * Aggregated reverse layout value that takes RTL into account.
136     */
137    boolean mShouldReverseLayout = false;
138
139    /**
140     * Temporary variable used during fill method to check which spans needs to be filled.
141     */
142    private BitSet mRemainingSpans;
143
144    /**
145     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
146     * layout which will check this variable and re-layout accordingly.
147     */
148    int mPendingScrollPosition = NO_POSITION;
149
150    /**
151     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
152     * called.
153     */
154    int mPendingScrollPositionOffset = INVALID_OFFSET;
155
156    /**
157     * Keeps the mapping between the adapter positions and spans. This is necessary to provide
158     * a consistent experience when user scrolls the list.
159     */
160    LazySpanLookup mLazySpanLookup = new LazySpanLookup();
161
162    /**
163     * how we handle gaps in UI.
164     */
165    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
166
167    /**
168     * Saved state needs this information to properly layout on restore.
169     */
170    private boolean mLastLayoutFromEnd;
171
172    /**
173     * Saved state and onLayout needs this information to re-layout properly
174     */
175    private boolean mLastLayoutRTL;
176
177    /**
178     * SavedState is not handled until a layout happens. This is where we keep it until next
179     * layout.
180     */
181    private SavedState mPendingSavedState;
182
183    /**
184     * Re-used measurement specs. updated by onLayout.
185     */
186    private int mFullSizeSpec;
187
188    /**
189     * Re-used rectangle to get child decor offsets.
190     */
191    private final Rect mTmpRect = new Rect();
192
193    /**
194     * Re-used anchor info.
195     */
196    private final AnchorInfo mAnchorInfo = new AnchorInfo();
197
198    /**
199     * If a full span item is invalid / or created in reverse direction; it may create gaps in
200     * the UI. While laying out, if such case is detected, we set this flag.
201     * <p>
202     * After scrolling stops, we check this flag and if it is set, re-layout.
203     */
204    private boolean mLaidOutInvalidFullSpan = false;
205
206    /**
207     * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
208     * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
209     */
210    private boolean mSmoothScrollbarEnabled = true;
211
212    private final Runnable mCheckForGapsRunnable = new Runnable() {
213        @Override
214        public void run() {
215            checkForGaps();
216        }
217    };
218
219    /**
220     * Constructor used when layout manager is set in XML by RecyclerView attribute
221     * "layoutManager". Defaults to single column and vertical.
222     */
223    @SuppressWarnings("unused")
224    public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
225            int defStyleRes) {
226        Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
227        setOrientation(properties.orientation);
228        setSpanCount(properties.spanCount);
229        setReverseLayout(properties.reverseLayout);
230        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
231        mLayoutState = new LayoutState();
232        createOrientationHelpers();
233    }
234
235    /**
236     * Creates a StaggeredGridLayoutManager with given parameters.
237     *
238     * @param spanCount   If orientation is vertical, spanCount is number of columns. If
239     *                    orientation is horizontal, spanCount is number of rows.
240     * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
241     */
242    public StaggeredGridLayoutManager(int spanCount, int orientation) {
243        mOrientation = orientation;
244        setSpanCount(spanCount);
245        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
246        mLayoutState = new LayoutState();
247        createOrientationHelpers();
248    }
249
250    private void createOrientationHelpers() {
251        mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
252        mSecondaryOrientation = OrientationHelper
253                .createOrientationHelper(this, 1 - mOrientation);
254    }
255
256    /**
257     * Checks for gaps in the UI that may be caused by adapter changes.
258     * <p>
259     * When a full span item is laid out in reverse direction, it sets a flag which we check when
260     * scroll is stopped (or re-layout happens) and re-layout after first valid item.
261     */
262    private boolean checkForGaps() {
263        if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) {
264            return false;
265        }
266        final int minPos, maxPos;
267        if (mShouldReverseLayout) {
268            minPos = getLastChildPosition();
269            maxPos = getFirstChildPosition();
270        } else {
271            minPos = getFirstChildPosition();
272            maxPos = getLastChildPosition();
273        }
274        if (minPos == 0) {
275            View gapView = hasGapsToFix();
276            if (gapView != null) {
277                mLazySpanLookup.clear();
278                requestSimpleAnimationsInNextLayout();
279                requestLayout();
280                return true;
281            }
282        }
283        if (!mLaidOutInvalidFullSpan) {
284            return false;
285        }
286        int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
287        final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
288                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
289        if (invalidFsi == null) {
290            mLaidOutInvalidFullSpan = false;
291            mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
292            return false;
293        }
294        final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
295                .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
296                        invalidGapDir * -1, true);
297        if (validFsi == null) {
298            mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
299        } else {
300            mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
301        }
302        requestSimpleAnimationsInNextLayout();
303        requestLayout();
304        return true;
305    }
306
307    @Override
308    public void onScrollStateChanged(int state) {
309        if (state == RecyclerView.SCROLL_STATE_IDLE) {
310            checkForGaps();
311        }
312    }
313
314    @Override
315    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
316        removeCallbacks(mCheckForGapsRunnable);
317        for (int i = 0; i < mSpanCount; i++) {
318            mSpans[i].clear();
319        }
320        // SGLM will require fresh layout call to recover state after detach
321        view.requestLayout();
322    }
323
324    /**
325     * Checks for gaps if we've reached to the top of the list.
326     * <p>
327     * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
328     */
329    View hasGapsToFix() {
330        int startChildIndex = 0;
331        int endChildIndex = getChildCount() - 1;
332        BitSet mSpansToCheck = new BitSet(mSpanCount);
333        mSpansToCheck.set(0, mSpanCount, true);
334
335        final int firstChildIndex, childLimit;
336        final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
337
338        if (mShouldReverseLayout) {
339            firstChildIndex = endChildIndex;
340            childLimit = startChildIndex - 1;
341        } else {
342            firstChildIndex = startChildIndex;
343            childLimit = endChildIndex + 1;
344        }
345        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
346        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
347            View child = getChildAt(i);
348            LayoutParams lp = (LayoutParams) child.getLayoutParams();
349            if (mSpansToCheck.get(lp.mSpan.mIndex)) {
350                if (checkSpanForGap(lp.mSpan)) {
351                    return child;
352                }
353                mSpansToCheck.clear(lp.mSpan.mIndex);
354            }
355            if (lp.mFullSpan) {
356                continue; // quick reject
357            }
358
359            if (i + nextChildDiff != childLimit) {
360                View nextChild = getChildAt(i + nextChildDiff);
361                boolean compareSpans = false;
362                if (mShouldReverseLayout) {
363                    // ensure child's end is below nextChild's end
364                    int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
365                    int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
366                    if (myEnd < nextEnd) {
367                        return child;//i should have a better position
368                    } else if (myEnd == nextEnd) {
369                        compareSpans = true;
370                    }
371                } else {
372                    int myStart = mPrimaryOrientation.getDecoratedStart(child);
373                    int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
374                    if (myStart > nextStart) {
375                        return child;//i should have a better position
376                    } else if (myStart == nextStart) {
377                        compareSpans = true;
378                    }
379                }
380                if (compareSpans) {
381                    // equal, check span indices.
382                    LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
383                    if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
384                        return child;
385                    }
386                }
387            }
388        }
389        // everything looks good
390        return null;
391    }
392
393    private boolean checkSpanForGap(Span span) {
394        if (mShouldReverseLayout) {
395            if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
396                // if it is full span, it is OK
397                final View endView = span.mViews.get(span.mViews.size() - 1);
398                final LayoutParams lp = span.getLayoutParams(endView);
399                return !lp.mFullSpan;
400            }
401        } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
402            // if it is full span, it is OK
403            final View startView = span.mViews.get(0);
404            final LayoutParams lp = span.getLayoutParams(startView);
405            return !lp.mFullSpan;
406        }
407        return false;
408    }
409
410    /**
411     * Sets the number of spans for the layout. This will invalidate all of the span assignments
412     * for Views.
413     * <p>
414     * Calling this method will automatically result in a new layout request unless the spanCount
415     * parameter is equal to current span count.
416     *
417     * @param spanCount Number of spans to layout
418     */
419    public void setSpanCount(int spanCount) {
420        assertNotInLayoutOrScroll(null);
421        if (spanCount != mSpanCount) {
422            invalidateSpanAssignments();
423            mSpanCount = spanCount;
424            mRemainingSpans = new BitSet(mSpanCount);
425            mSpans = new Span[mSpanCount];
426            for (int i = 0; i < mSpanCount; i++) {
427                mSpans[i] = new Span(i);
428            }
429            requestLayout();
430        }
431    }
432
433    /**
434     * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
435     * scroll position if this method is called after views are laid out.
436     *
437     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
438     */
439    public void setOrientation(int orientation) {
440        if (orientation != HORIZONTAL && orientation != VERTICAL) {
441            throw new IllegalArgumentException("invalid orientation.");
442        }
443        assertNotInLayoutOrScroll(null);
444        if (orientation == mOrientation) {
445            return;
446        }
447        mOrientation = orientation;
448        OrientationHelper tmp = mPrimaryOrientation;
449        mPrimaryOrientation = mSecondaryOrientation;
450        mSecondaryOrientation = tmp;
451        requestLayout();
452    }
453
454    /**
455     * Sets whether LayoutManager should start laying out items from the end of the UI. The order
456     * items are traversed is not affected by this call.
457     * <p>
458     * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
459     * the list.
460     * <p>
461     * For horizontal layouts, it depends on the layout direction.
462     * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
463     * {@link RecyclerView}} is RTL, it will layout from LTR.
464     *
465     * @param reverseLayout Whether layout should be in reverse or not
466     */
467    public void setReverseLayout(boolean reverseLayout) {
468        assertNotInLayoutOrScroll(null);
469        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
470            mPendingSavedState.mReverseLayout = reverseLayout;
471        }
472        mReverseLayout = reverseLayout;
473        requestLayout();
474    }
475
476    /**
477     * Returns the current gap handling strategy for StaggeredGridLayoutManager.
478     * <p>
479     * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
480     * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
481     * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
482     * <p>
483     * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
484     *
485     * @return Current gap handling strategy.
486     * @see #setGapStrategy(int)
487     * @see #GAP_HANDLING_NONE
488     * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
489     */
490    public int getGapStrategy() {
491        return mGapStrategy;
492    }
493
494    /**
495     * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
496     * is different than the current strategy, calling this method will trigger a layout request.
497     *
498     * @param gapStrategy The new gap handling strategy. Should be
499     *                    {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
500     *                    #GAP_HANDLING_NONE}.
501     * @see #getGapStrategy()
502     */
503    public void setGapStrategy(int gapStrategy) {
504        assertNotInLayoutOrScroll(null);
505        if (gapStrategy == mGapStrategy) {
506            return;
507        }
508        if (gapStrategy != GAP_HANDLING_NONE &&
509                gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
510            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
511                    + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
512        }
513        mGapStrategy = gapStrategy;
514        setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
515        requestLayout();
516    }
517
518    @Override
519    public void assertNotInLayoutOrScroll(String message) {
520        if (mPendingSavedState == null) {
521            super.assertNotInLayoutOrScroll(message);
522        }
523    }
524
525    /**
526     * Returns the number of spans laid out by StaggeredGridLayoutManager.
527     *
528     * @return Number of spans in the layout
529     */
530    public int getSpanCount() {
531        return mSpanCount;
532    }
533
534    /**
535     * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
536     * <p>
537     * If you need to cancel current assignments, you can call this method which will clear all
538     * assignments and request a new layout.
539     */
540    public void invalidateSpanAssignments() {
541        mLazySpanLookup.clear();
542        requestLayout();
543    }
544
545    /**
546     * Calculates the views' layout order. (e.g. from end to start or start to end)
547     * RTL layout support is applied automatically. So if layout is RTL and
548     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
549     */
550    private void resolveShouldLayoutReverse() {
551        // A == B is the same result, but we rather keep it readable
552        if (mOrientation == VERTICAL || !isLayoutRTL()) {
553            mShouldReverseLayout = mReverseLayout;
554        } else {
555            mShouldReverseLayout = !mReverseLayout;
556        }
557    }
558
559    boolean isLayoutRTL() {
560        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
561    }
562
563    /**
564     * Returns whether views are laid out in reverse order or not.
565     * <p>
566     * Not that this value is not affected by RecyclerView's layout direction.
567     *
568     * @return True if layout is reversed, false otherwise
569     * @see #setReverseLayout(boolean)
570     */
571    public boolean getReverseLayout() {
572        return mReverseLayout;
573    }
574
575    @Override
576    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
577        // we don't like it to wrap content in our non-scroll direction.
578        final int width, height;
579        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
580        final int verticalPadding = getPaddingTop() + getPaddingBottom();
581        if (mOrientation == VERTICAL) {
582            final int usedHeight = childrenBounds.height() + verticalPadding;
583            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
584            width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding,
585                    getMinimumWidth());
586        } else {
587            final int usedWidth = childrenBounds.width() + horizontalPadding;
588            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
589            height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding,
590                    getMinimumHeight());
591        }
592        setMeasuredDimension(width, height);
593    }
594
595    @Override
596    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
597        onLayoutChildren(recycler, state, true);
598    }
599
600
601    private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state,
602            boolean shouldCheckForGaps) {
603        final AnchorInfo anchorInfo = mAnchorInfo;
604        if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
605            if (state.getItemCount() == 0) {
606                removeAndRecycleAllViews(recycler);
607                anchorInfo.reset();
608                return;
609            }
610        }
611
612        if (!anchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
613                mPendingSavedState != null) {
614            anchorInfo.reset();
615            if (mPendingSavedState != null) {
616                applyPendingSavedState(anchorInfo);
617            } else {
618                resolveShouldLayoutReverse();
619                anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
620            }
621
622            updateAnchorInfoForLayout(state, anchorInfo);
623            anchorInfo.mValid = true;
624        }
625        if (mPendingSavedState == null && mPendingScrollPosition == NO_POSITION) {
626            if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
627                    isLayoutRTL() != mLastLayoutRTL) {
628                mLazySpanLookup.clear();
629                anchorInfo.mInvalidateOffsets = true;
630            }
631        }
632
633        if (getChildCount() > 0 && (mPendingSavedState == null ||
634                mPendingSavedState.mSpanOffsetsSize < 1)) {
635            if (anchorInfo.mInvalidateOffsets) {
636                for (int i = 0; i < mSpanCount; i++) {
637                    // Scroll to position is set, clear.
638                    mSpans[i].clear();
639                    if (anchorInfo.mOffset != INVALID_OFFSET) {
640                        mSpans[i].setLine(anchorInfo.mOffset);
641                    }
642                }
643            } else {
644                for (int i = 0; i < mSpanCount; i++) {
645                    mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset);
646                }
647            }
648        }
649        detachAndScrapAttachedViews(recycler);
650        mLayoutState.mRecycle = false;
651        mLaidOutInvalidFullSpan = false;
652        updateMeasureSpecs(mSecondaryOrientation.getTotalSpace());
653        updateLayoutState(anchorInfo.mPosition, state);
654        if (anchorInfo.mLayoutFromEnd) {
655            // Layout start.
656            setLayoutStateDirection(LAYOUT_START);
657            fill(recycler, mLayoutState, state);
658            // Layout end.
659            setLayoutStateDirection(LAYOUT_END);
660            mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
661            fill(recycler, mLayoutState, state);
662        } else {
663            // Layout end.
664            setLayoutStateDirection(LAYOUT_END);
665            fill(recycler, mLayoutState, state);
666            // Layout start.
667            setLayoutStateDirection(LAYOUT_START);
668            mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
669            fill(recycler, mLayoutState, state);
670        }
671
672        repositionToWrapContentIfNecessary();
673
674        if (getChildCount() > 0) {
675            if (mShouldReverseLayout) {
676                fixEndGap(recycler, state, true);
677                fixStartGap(recycler, state, false);
678            } else {
679                fixStartGap(recycler, state, true);
680                fixEndGap(recycler, state, false);
681            }
682        }
683        boolean hasGaps = false;
684        if (shouldCheckForGaps && !state.isPreLayout()) {
685            final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
686                    && getChildCount() > 0
687                    && (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
688            if (needToCheckForGaps) {
689                removeCallbacks(mCheckForGapsRunnable);
690                if (checkForGaps()) {
691                    hasGaps = true;
692                }
693            }
694        }
695        if (state.isPreLayout()) {
696            mAnchorInfo.reset();
697        }
698        mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
699        mLastLayoutRTL = isLayoutRTL();
700        if (hasGaps) {
701            mAnchorInfo.reset();
702            onLayoutChildren(recycler, state, false);
703        }
704    }
705
706    @Override
707    public void onLayoutCompleted(RecyclerView.State state) {
708        super.onLayoutCompleted(state);
709        mPendingScrollPosition = NO_POSITION;
710        mPendingScrollPositionOffset = INVALID_OFFSET;
711        mPendingSavedState = null; // we don't need this anymore
712        mAnchorInfo.reset();
713    }
714
715    private void repositionToWrapContentIfNecessary() {
716        if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
717            return; // nothing to do
718        }
719        float maxSize = 0;
720        final int childCount = getChildCount();
721        for (int i = 0; i < childCount; i ++) {
722            View child = getChildAt(i);
723            float size = mSecondaryOrientation.getDecoratedMeasurement(child);
724            if (size < maxSize) {
725                continue;
726            }
727            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
728            if (layoutParams.isFullSpan()) {
729                size = 1f * size / mSpanCount;
730            }
731            maxSize = Math.max(maxSize, size);
732        }
733        int before = mSizePerSpan;
734        int desired = Math.round(maxSize * mSpanCount);
735        if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) {
736            desired = Math.min(desired, mSecondaryOrientation.getTotalSpace());
737        }
738        updateMeasureSpecs(desired);
739        if (mSizePerSpan == before) {
740            return; // nothing has changed
741        }
742        for (int i = 0; i < childCount; i ++) {
743            View child = getChildAt(i);
744            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
745            if (lp.mFullSpan) {
746                continue;
747            }
748            if (isLayoutRTL() && mOrientation == VERTICAL) {
749                int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan;
750                int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before;
751                child.offsetLeftAndRight(newOffset - prevOffset);
752            } else {
753                int newOffset = lp.mSpan.mIndex * mSizePerSpan;
754                int prevOffset = lp.mSpan.mIndex * before;
755                if (mOrientation == VERTICAL) {
756                    child.offsetLeftAndRight(newOffset - prevOffset);
757                } else {
758                    child.offsetTopAndBottom(newOffset - prevOffset);
759                }
760            }
761        }
762    }
763
764    private void applyPendingSavedState(AnchorInfo anchorInfo) {
765        if (DEBUG) {
766            Log.d(TAG, "found saved state: " + mPendingSavedState);
767        }
768        if (mPendingSavedState.mSpanOffsetsSize > 0) {
769            if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
770                for (int i = 0; i < mSpanCount; i++) {
771                    mSpans[i].clear();
772                    int line = mPendingSavedState.mSpanOffsets[i];
773                    if (line != Span.INVALID_LINE) {
774                        if (mPendingSavedState.mAnchorLayoutFromEnd) {
775                            line += mPrimaryOrientation.getEndAfterPadding();
776                        } else {
777                            line += mPrimaryOrientation.getStartAfterPadding();
778                        }
779                    }
780                    mSpans[i].setLine(line);
781                }
782            } else {
783                mPendingSavedState.invalidateSpanInfo();
784                mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
785            }
786        }
787        mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
788        setReverseLayout(mPendingSavedState.mReverseLayout);
789        resolveShouldLayoutReverse();
790
791        if (mPendingSavedState.mAnchorPosition != NO_POSITION) {
792            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
793            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
794        } else {
795            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
796        }
797        if (mPendingSavedState.mSpanLookupSize > 1) {
798            mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
799            mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
800        }
801    }
802
803    void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
804        if (updateAnchorFromPendingData(state, anchorInfo)) {
805            return;
806        }
807        if (updateAnchorFromChildren(state, anchorInfo)) {
808            return;
809        }
810        if (DEBUG) {
811            Log.d(TAG, "Deciding anchor info from fresh state");
812        }
813        anchorInfo.assignCoordinateFromPadding();
814        anchorInfo.mPosition = 0;
815    }
816
817    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
818        // We don't recycle views out of adapter order. This way, we can rely on the first or
819        // last child as the anchor position.
820        // Layout direction may change but we should select the child depending on the latest
821        // layout direction. Otherwise, we'll choose the wrong child.
822        anchorInfo.mPosition = mLastLayoutFromEnd
823                ? findLastReferenceChildPosition(state.getItemCount())
824                : findFirstReferenceChildPosition(state.getItemCount());
825        anchorInfo.mOffset = INVALID_OFFSET;
826        return true;
827    }
828
829    boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
830        // Validate scroll position if exists.
831        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
832            return false;
833        }
834        // Validate it.
835        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
836            mPendingScrollPosition = NO_POSITION;
837            mPendingScrollPositionOffset = INVALID_OFFSET;
838            return false;
839        }
840
841        if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION
842                || mPendingSavedState.mSpanOffsetsSize < 1) {
843            // If item is visible, make it fully visible.
844            final View child = findViewByPosition(mPendingScrollPosition);
845            if (child != null) {
846                // Use regular anchor position, offset according to pending offset and target
847                // child
848                anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
849                        : getFirstChildPosition();
850                if (mPendingScrollPositionOffset != INVALID_OFFSET) {
851                    if (anchorInfo.mLayoutFromEnd) {
852                        final int target = mPrimaryOrientation.getEndAfterPadding() -
853                                mPendingScrollPositionOffset;
854                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
855                    } else {
856                        final int target = mPrimaryOrientation.getStartAfterPadding() +
857                                mPendingScrollPositionOffset;
858                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
859                    }
860                    return true;
861                }
862
863                // no offset provided. Decide according to the child location
864                final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
865                if (childSize > mPrimaryOrientation.getTotalSpace()) {
866                    // Item does not fit. Fix depending on layout direction.
867                    anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
868                            ? mPrimaryOrientation.getEndAfterPadding()
869                            : mPrimaryOrientation.getStartAfterPadding();
870                    return true;
871                }
872
873                final int startGap = mPrimaryOrientation.getDecoratedStart(child)
874                        - mPrimaryOrientation.getStartAfterPadding();
875                if (startGap < 0) {
876                    anchorInfo.mOffset = -startGap;
877                    return true;
878                }
879                final int endGap = mPrimaryOrientation.getEndAfterPadding() -
880                        mPrimaryOrientation.getDecoratedEnd(child);
881                if (endGap < 0) {
882                    anchorInfo.mOffset = endGap;
883                    return true;
884                }
885                // child already visible. just layout as usual
886                anchorInfo.mOffset = INVALID_OFFSET;
887            } else {
888                // Child is not visible. Set anchor coordinate depending on in which direction
889                // child will be visible.
890                anchorInfo.mPosition = mPendingScrollPosition;
891                if (mPendingScrollPositionOffset == INVALID_OFFSET) {
892                    final int position = calculateScrollDirectionForPosition(
893                            anchorInfo.mPosition);
894                    anchorInfo.mLayoutFromEnd = position == LAYOUT_END;
895                    anchorInfo.assignCoordinateFromPadding();
896                } else {
897                    anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
898                }
899                anchorInfo.mInvalidateOffsets = true;
900            }
901        } else {
902            anchorInfo.mOffset = INVALID_OFFSET;
903            anchorInfo.mPosition = mPendingScrollPosition;
904        }
905        return true;
906    }
907
908    void updateMeasureSpecs(int totalSpace) {
909        mSizePerSpan = totalSpace / mSpanCount;
910        //noinspection ResourceType
911        mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
912                totalSpace, mSecondaryOrientation.getMode());
913    }
914
915    @Override
916    public boolean supportsPredictiveItemAnimations() {
917        return mPendingSavedState == null;
918    }
919
920    /**
921     * Returns the adapter position of the first visible view for each span.
922     * <p>
923     * Note that, this value is not affected by layout orientation or item order traversal.
924     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
925     * not in the layout.
926     * <p>
927     * If RecyclerView has item decorators, they will be considered in calculations as well.
928     * <p>
929     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
930     * views are ignored in this method.
931     *
932     * @param into An array to put the results into. If you don't provide any, LayoutManager will
933     *             create a new one.
934     * @return The adapter position of the first visible item in each span. If a span does not have
935     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
936     * @see #findFirstCompletelyVisibleItemPositions(int[])
937     * @see #findLastVisibleItemPositions(int[])
938     */
939    public int[] findFirstVisibleItemPositions(int[] into) {
940        if (into == null) {
941            into = new int[mSpanCount];
942        } else if (into.length < mSpanCount) {
943            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
944                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
945        }
946        for (int i = 0; i < mSpanCount; i++) {
947            into[i] = mSpans[i].findFirstVisibleItemPosition();
948        }
949        return into;
950    }
951
952    /**
953     * Returns the adapter position of the first completely visible view for each span.
954     * <p>
955     * Note that, this value is not affected by layout orientation or item order traversal.
956     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
957     * not in the layout.
958     * <p>
959     * If RecyclerView has item decorators, they will be considered in calculations as well.
960     * <p>
961     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
962     * views are ignored in this method.
963     *
964     * @param into An array to put the results into. If you don't provide any, LayoutManager will
965     *             create a new one.
966     * @return The adapter position of the first fully visible item in each span. If a span does
967     * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
968     * @see #findFirstVisibleItemPositions(int[])
969     * @see #findLastCompletelyVisibleItemPositions(int[])
970     */
971    public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
972        if (into == null) {
973            into = new int[mSpanCount];
974        } else if (into.length < mSpanCount) {
975            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
976                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
977        }
978        for (int i = 0; i < mSpanCount; i++) {
979            into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
980        }
981        return into;
982    }
983
984    /**
985     * Returns the adapter position of the last visible view for each span.
986     * <p>
987     * Note that, this value is not affected by layout orientation or item order traversal.
988     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
989     * not in the layout.
990     * <p>
991     * If RecyclerView has item decorators, they will be considered in calculations as well.
992     * <p>
993     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
994     * views are ignored in this method.
995     *
996     * @param into An array to put the results into. If you don't provide any, LayoutManager will
997     *             create a new one.
998     * @return The adapter position of the last visible item in each span. If a span does not have
999     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
1000     * @see #findLastCompletelyVisibleItemPositions(int[])
1001     * @see #findFirstVisibleItemPositions(int[])
1002     */
1003    public int[] findLastVisibleItemPositions(int[] into) {
1004        if (into == null) {
1005            into = new int[mSpanCount];
1006        } else if (into.length < mSpanCount) {
1007            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
1008                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
1009        }
1010        for (int i = 0; i < mSpanCount; i++) {
1011            into[i] = mSpans[i].findLastVisibleItemPosition();
1012        }
1013        return into;
1014    }
1015
1016    /**
1017     * Returns the adapter position of the last completely visible view for each span.
1018     * <p>
1019     * Note that, this value is not affected by layout orientation or item order traversal.
1020     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
1021     * not in the layout.
1022     * <p>
1023     * If RecyclerView has item decorators, they will be considered in calculations as well.
1024     * <p>
1025     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
1026     * views are ignored in this method.
1027     *
1028     * @param into An array to put the results into. If you don't provide any, LayoutManager will
1029     *             create a new one.
1030     * @return The adapter position of the last fully visible item in each span. If a span does not
1031     * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
1032     * @see #findFirstCompletelyVisibleItemPositions(int[])
1033     * @see #findLastVisibleItemPositions(int[])
1034     */
1035    public int[] findLastCompletelyVisibleItemPositions(int[] into) {
1036        if (into == null) {
1037            into = new int[mSpanCount];
1038        } else if (into.length < mSpanCount) {
1039            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
1040                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
1041        }
1042        for (int i = 0; i < mSpanCount; i++) {
1043            into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
1044        }
1045        return into;
1046    }
1047
1048    @Override
1049    public int computeHorizontalScrollOffset(RecyclerView.State state) {
1050        return computeScrollOffset(state);
1051    }
1052
1053    private int computeScrollOffset(RecyclerView.State state) {
1054        if (getChildCount() == 0) {
1055            return 0;
1056        }
1057        return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
1058                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
1059                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
1060                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
1061    }
1062
1063    @Override
1064    public int computeVerticalScrollOffset(RecyclerView.State state) {
1065        return computeScrollOffset(state);
1066    }
1067
1068    @Override
1069    public int computeHorizontalScrollExtent(RecyclerView.State state) {
1070        return computeScrollExtent(state);
1071    }
1072
1073    private int computeScrollExtent(RecyclerView.State state) {
1074        if (getChildCount() == 0) {
1075            return 0;
1076        }
1077        return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
1078                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
1079                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
1080                this, mSmoothScrollbarEnabled);
1081    }
1082
1083    @Override
1084    public int computeVerticalScrollExtent(RecyclerView.State state) {
1085        return computeScrollExtent(state);
1086    }
1087
1088    @Override
1089    public int computeHorizontalScrollRange(RecyclerView.State state) {
1090        return computeScrollRange(state);
1091    }
1092
1093    private int computeScrollRange(RecyclerView.State state) {
1094        if (getChildCount() == 0) {
1095            return 0;
1096        }
1097        return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
1098                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
1099                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
1100                this, mSmoothScrollbarEnabled);
1101    }
1102
1103    @Override
1104    public int computeVerticalScrollRange(RecyclerView.State state) {
1105        return computeScrollRange(state);
1106    }
1107
1108    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp,
1109            boolean alreadyMeasured) {
1110        if (lp.mFullSpan) {
1111            if (mOrientation == VERTICAL) {
1112                measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
1113                        getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
1114                        alreadyMeasured);
1115            } else {
1116                measureChildWithDecorationsAndMargin(child,
1117                        getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
1118                        mFullSizeSpec, alreadyMeasured);
1119            }
1120        } else {
1121            if (mOrientation == VERTICAL) {
1122                measureChildWithDecorationsAndMargin(child,
1123                        getChildMeasureSpec(mSizePerSpan, getWidthMode(), 0, lp.width, false),
1124                        getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
1125                        alreadyMeasured);
1126            } else {
1127                measureChildWithDecorationsAndMargin(child,
1128                        getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
1129                        getChildMeasureSpec(mSizePerSpan, getHeightMode(), 0, lp.height, false),
1130                        alreadyMeasured);
1131            }
1132        }
1133    }
1134
1135    private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
1136            int heightSpec, boolean alreadyMeasured) {
1137        calculateItemDecorationsForChild(child, mTmpRect);
1138        LayoutParams lp = (LayoutParams) child.getLayoutParams();
1139        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
1140                lp.rightMargin + mTmpRect.right);
1141        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
1142                lp.bottomMargin + mTmpRect.bottom);
1143        final boolean measure = alreadyMeasured
1144                ? shouldReMeasureChild(child, widthSpec, heightSpec, lp)
1145                : shouldMeasureChild(child, widthSpec, heightSpec, lp);
1146        if (measure) {
1147            child.measure(widthSpec, heightSpec);
1148        }
1149
1150    }
1151
1152    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
1153        if (startInset == 0 && endInset == 0) {
1154            return spec;
1155        }
1156        final int mode = View.MeasureSpec.getMode(spec);
1157        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
1158            return View.MeasureSpec.makeMeasureSpec(
1159                    Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode);
1160        }
1161        return spec;
1162    }
1163
1164    @Override
1165    public void onRestoreInstanceState(Parcelable state) {
1166        if (state instanceof SavedState) {
1167            mPendingSavedState = (SavedState) state;
1168            requestLayout();
1169        } else if (DEBUG) {
1170            Log.d(TAG, "invalid saved state class");
1171        }
1172    }
1173
1174    @Override
1175    public Parcelable onSaveInstanceState() {
1176        if (mPendingSavedState != null) {
1177            return new SavedState(mPendingSavedState);
1178        }
1179        SavedState state = new SavedState();
1180        state.mReverseLayout = mReverseLayout;
1181        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
1182        state.mLastLayoutRTL = mLastLayoutRTL;
1183
1184        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
1185            state.mSpanLookup = mLazySpanLookup.mData;
1186            state.mSpanLookupSize = state.mSpanLookup.length;
1187            state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
1188        } else {
1189            state.mSpanLookupSize = 0;
1190        }
1191
1192        if (getChildCount() > 0) {
1193            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
1194                    : getFirstChildPosition();
1195            state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
1196            state.mSpanOffsetsSize = mSpanCount;
1197            state.mSpanOffsets = new int[mSpanCount];
1198            for (int i = 0; i < mSpanCount; i++) {
1199                int line;
1200                if (mLastLayoutFromEnd) {
1201                    line = mSpans[i].getEndLine(Span.INVALID_LINE);
1202                    if (line != Span.INVALID_LINE) {
1203                        line -= mPrimaryOrientation.getEndAfterPadding();
1204                    }
1205                } else {
1206                    line = mSpans[i].getStartLine(Span.INVALID_LINE);
1207                    if (line != Span.INVALID_LINE) {
1208                        line -= mPrimaryOrientation.getStartAfterPadding();
1209                    }
1210                }
1211                state.mSpanOffsets[i] = line;
1212            }
1213        } else {
1214            state.mAnchorPosition = NO_POSITION;
1215            state.mVisibleAnchorPosition = NO_POSITION;
1216            state.mSpanOffsetsSize = 0;
1217        }
1218        if (DEBUG) {
1219            Log.d(TAG, "saved state:\n" + state);
1220        }
1221        return state;
1222    }
1223
1224    @Override
1225    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
1226            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
1227        ViewGroup.LayoutParams lp = host.getLayoutParams();
1228        if (!(lp instanceof LayoutParams)) {
1229            super.onInitializeAccessibilityNodeInfoForItem(host, info);
1230            return;
1231        }
1232        LayoutParams sglp = (LayoutParams) lp;
1233        if (mOrientation == HORIZONTAL) {
1234            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1235                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1236                    -1, -1,
1237                    sglp.mFullSpan, false));
1238        } else { // VERTICAL
1239            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1240                    -1, -1,
1241                    sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1242                    sglp.mFullSpan, false));
1243        }
1244    }
1245
1246    @Override
1247    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1248        super.onInitializeAccessibilityEvent(event);
1249        if (getChildCount() > 0) {
1250            final AccessibilityRecordCompat record = AccessibilityEventCompat
1251                    .asRecord(event);
1252            final View start = findFirstVisibleItemClosestToStart(false, true);
1253            final View end = findFirstVisibleItemClosestToEnd(false, true);
1254            if (start == null || end == null) {
1255                return;
1256            }
1257            final int startPos = getPosition(start);
1258            final int endPos = getPosition(end);
1259            if (startPos < endPos) {
1260                record.setFromIndex(startPos);
1261                record.setToIndex(endPos);
1262            } else {
1263                record.setFromIndex(endPos);
1264                record.setToIndex(startPos);
1265            }
1266        }
1267    }
1268
1269    /**
1270     * Finds the first fully visible child to be used as an anchor child if span count changes when
1271     * state is restored. If no children is fully visible, returns a partially visible child instead
1272     * of returning null.
1273     */
1274    int findFirstVisibleItemPositionInt() {
1275        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) :
1276                findFirstVisibleItemClosestToStart(true, true);
1277        return first == null ? NO_POSITION : getPosition(first);
1278    }
1279
1280    @Override
1281    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
1282            RecyclerView.State state) {
1283        if (mOrientation == HORIZONTAL) {
1284            return mSpanCount;
1285        }
1286        return super.getRowCountForAccessibility(recycler, state);
1287    }
1288
1289    @Override
1290    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
1291            RecyclerView.State state) {
1292        if (mOrientation == VERTICAL) {
1293            return mSpanCount;
1294        }
1295        return super.getColumnCountForAccessibility(recycler, state);
1296    }
1297
1298    /**
1299     * This is for internal use. Not necessarily the child closest to start but the first child
1300     * we find that matches the criteria.
1301     * This method does not do any sorting based on child's start coordinate, instead, it uses
1302     * children order.
1303     */
1304    View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) {
1305        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1306        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1307        final int limit = getChildCount();
1308        View partiallyVisible = null;
1309        for (int i = 0; i < limit; i++) {
1310            final View child = getChildAt(i);
1311            final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1312            final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1313            if(childEnd <= boundsStart || childStart >= boundsEnd) {
1314                continue; // not visible at all
1315            }
1316            if (childStart >= boundsStart || !fullyVisible) {
1317                // when checking for start, it is enough even if part of the child's top is visible
1318                // as long as fully visible is not requested.
1319                return child;
1320            }
1321            if (acceptPartiallyVisible && partiallyVisible == null) {
1322                partiallyVisible = child;
1323            }
1324        }
1325        return partiallyVisible;
1326    }
1327
1328    /**
1329     * This is for internal use. Not necessarily the child closest to bottom but the first child
1330     * we find that matches the criteria.
1331     * This method does not do any sorting based on child's end coordinate, instead, it uses
1332     * children order.
1333     */
1334    View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) {
1335        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1336        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1337        View partiallyVisible = null;
1338        for (int i = getChildCount() - 1; i >= 0; i--) {
1339            final View child = getChildAt(i);
1340            final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1341            final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1342            if(childEnd <= boundsStart || childStart >= boundsEnd) {
1343                continue; // not visible at all
1344            }
1345            if (childEnd <= boundsEnd || !fullyVisible) {
1346                // when checking for end, it is enough even if part of the child's bottom is visible
1347                // as long as fully visible is not requested.
1348                return child;
1349            }
1350            if (acceptPartiallyVisible && partiallyVisible == null) {
1351                partiallyVisible = child;
1352            }
1353        }
1354        return partiallyVisible;
1355    }
1356
1357    private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1358            boolean canOffsetChildren) {
1359        final int maxEndLine = getMaxEnd(Integer.MIN_VALUE);
1360        if (maxEndLine == Integer.MIN_VALUE) {
1361            return;
1362        }
1363        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
1364        int fixOffset;
1365        if (gap > 0) {
1366            fixOffset = -scrollBy(-gap, recycler, state);
1367        } else {
1368            return; // nothing to fix
1369        }
1370        gap -= fixOffset;
1371        if (canOffsetChildren && gap > 0) {
1372            mPrimaryOrientation.offsetChildren(gap);
1373        }
1374    }
1375
1376    private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1377            boolean canOffsetChildren) {
1378        final int minStartLine = getMinStart(Integer.MAX_VALUE);
1379        if (minStartLine == Integer.MAX_VALUE) {
1380            return;
1381        }
1382        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
1383        int fixOffset;
1384        if (gap > 0) {
1385            fixOffset = scrollBy(gap, recycler, state);
1386        } else {
1387            return; // nothing to fix
1388        }
1389        gap -= fixOffset;
1390        if (canOffsetChildren && gap > 0) {
1391            mPrimaryOrientation.offsetChildren(-gap);
1392        }
1393    }
1394
1395    private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
1396        mLayoutState.mAvailable = 0;
1397        mLayoutState.mCurrentPosition = anchorPosition;
1398        int startExtra = 0;
1399        int endExtra = 0;
1400        if (isSmoothScrolling()) {
1401            final int targetPos = state.getTargetScrollPosition();
1402            if (targetPos != NO_POSITION) {
1403                if (mShouldReverseLayout == targetPos < anchorPosition) {
1404                    endExtra = mPrimaryOrientation.getTotalSpace();
1405                } else {
1406                    startExtra = mPrimaryOrientation.getTotalSpace();
1407                }
1408            }
1409        }
1410
1411        // Line of the furthest row.
1412        final boolean clipToPadding = getClipToPadding();
1413        if (clipToPadding) {
1414            mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
1415            mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
1416        } else {
1417            mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
1418            mLayoutState.mStartLine = -startExtra;
1419        }
1420        mLayoutState.mStopInFocusable = false;
1421        mLayoutState.mRecycle = true;
1422        mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED &&
1423                mPrimaryOrientation.getEnd() == 0;
1424    }
1425
1426    private void setLayoutStateDirection(int direction) {
1427        mLayoutState.mLayoutDirection = direction;
1428        mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LAYOUT_START)) ?
1429                ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
1430    }
1431
1432    @Override
1433    public void offsetChildrenHorizontal(int dx) {
1434        super.offsetChildrenHorizontal(dx);
1435        for (int i = 0; i < mSpanCount; i++) {
1436            mSpans[i].onOffset(dx);
1437        }
1438    }
1439
1440    @Override
1441    public void offsetChildrenVertical(int dy) {
1442        super.offsetChildrenVertical(dy);
1443        for (int i = 0; i < mSpanCount; i++) {
1444            mSpans[i].onOffset(dy);
1445        }
1446    }
1447
1448    @Override
1449    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
1450        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
1451    }
1452
1453    @Override
1454    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
1455        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
1456    }
1457
1458    @Override
1459    public void onItemsChanged(RecyclerView recyclerView) {
1460        mLazySpanLookup.clear();
1461        requestLayout();
1462    }
1463
1464    @Override
1465    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
1466        handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
1467    }
1468
1469    @Override
1470    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
1471            Object payload) {
1472        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
1473    }
1474
1475    /**
1476     * Checks whether it should invalidate span assignments in response to an adapter change.
1477     */
1478    private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
1479        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
1480        final int affectedRangeEnd;// exclusive
1481        final int affectedRangeStart;// inclusive
1482
1483        if (cmd == AdapterHelper.UpdateOp.MOVE) {
1484            if (positionStart < itemCountOrToPosition) {
1485                affectedRangeEnd = itemCountOrToPosition + 1;
1486                affectedRangeStart = positionStart;
1487            } else {
1488                affectedRangeEnd = positionStart + 1;
1489                affectedRangeStart = itemCountOrToPosition;
1490            }
1491        } else {
1492            affectedRangeStart = positionStart;
1493            affectedRangeEnd = positionStart + itemCountOrToPosition;
1494        }
1495
1496        mLazySpanLookup.invalidateAfter(affectedRangeStart);
1497        switch (cmd) {
1498            case AdapterHelper.UpdateOp.ADD:
1499                mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
1500                break;
1501            case AdapterHelper.UpdateOp.REMOVE:
1502                mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
1503                break;
1504            case AdapterHelper.UpdateOp.MOVE:
1505                // TODO optimize
1506                mLazySpanLookup.offsetForRemoval(positionStart, 1);
1507                mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
1508                break;
1509        }
1510
1511        if (affectedRangeEnd <= minPosition) {
1512            return;
1513        }
1514
1515        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
1516        if (affectedRangeStart <= maxPosition) {
1517            requestLayout();
1518        }
1519    }
1520
1521    private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
1522            RecyclerView.State state) {
1523        mRemainingSpans.set(0, mSpanCount, true);
1524        // The target position we are trying to reach.
1525        final int targetLine;
1526
1527        // Line of the furthest row.
1528        if (mLayoutState.mInfinite) {
1529            if (layoutState.mLayoutDirection == LAYOUT_END) {
1530                targetLine = Integer.MAX_VALUE;
1531            } else { // LAYOUT_START
1532                targetLine = Integer.MIN_VALUE;
1533            }
1534        } else {
1535            if (layoutState.mLayoutDirection == LAYOUT_END) {
1536                targetLine = layoutState.mEndLine + layoutState.mAvailable;
1537            } else { // LAYOUT_START
1538                targetLine = layoutState.mStartLine - layoutState.mAvailable;
1539            }
1540        }
1541
1542        updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
1543        if (DEBUG) {
1544            Log.d(TAG, "FILLING targetLine: " + targetLine + "," +
1545                    "remaining spans:" + mRemainingSpans + ", state: " + layoutState);
1546        }
1547
1548        // the default coordinate to add new view.
1549        final int defaultNewViewLine = mShouldReverseLayout
1550                ? mPrimaryOrientation.getEndAfterPadding()
1551                : mPrimaryOrientation.getStartAfterPadding();
1552        boolean added = false;
1553        while (layoutState.hasMore(state)
1554                && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) {
1555            View view = layoutState.next(recycler);
1556            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
1557            final int position = lp.getViewLayoutPosition();
1558            final int spanIndex = mLazySpanLookup.getSpan(position);
1559            Span currentSpan;
1560            final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
1561            if (assignSpan) {
1562                currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
1563                mLazySpanLookup.setSpan(position, currentSpan);
1564                if (DEBUG) {
1565                    Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
1566                }
1567            } else {
1568                if (DEBUG) {
1569                    Log.d(TAG, "using " + spanIndex + " for pos " + position);
1570                }
1571                currentSpan = mSpans[spanIndex];
1572            }
1573            // assign span before measuring so that item decorators can get updated span index
1574            lp.mSpan = currentSpan;
1575            if (layoutState.mLayoutDirection == LAYOUT_END) {
1576                addView(view);
1577            } else {
1578                addView(view, 0);
1579            }
1580            measureChildWithDecorationsAndMargin(view, lp, false);
1581
1582            final int start;
1583            final int end;
1584            if (layoutState.mLayoutDirection == LAYOUT_END) {
1585                start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
1586                        : currentSpan.getEndLine(defaultNewViewLine);
1587                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
1588                if (assignSpan && lp.mFullSpan) {
1589                    LazySpanLookup.FullSpanItem fullSpanItem;
1590                    fullSpanItem = createFullSpanItemFromEnd(start);
1591                    fullSpanItem.mGapDir = LAYOUT_START;
1592                    fullSpanItem.mPosition = position;
1593                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1594                }
1595            } else {
1596                end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
1597                        : currentSpan.getStartLine(defaultNewViewLine);
1598                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
1599                if (assignSpan && lp.mFullSpan) {
1600                    LazySpanLookup.FullSpanItem fullSpanItem;
1601                    fullSpanItem = createFullSpanItemFromStart(end);
1602                    fullSpanItem.mGapDir = LAYOUT_END;
1603                    fullSpanItem.mPosition = position;
1604                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
1605                }
1606            }
1607
1608            // check if this item may create gaps in the future
1609            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
1610                if (assignSpan) {
1611                    mLaidOutInvalidFullSpan = true;
1612                } else {
1613                    final boolean hasInvalidGap;
1614                    if (layoutState.mLayoutDirection == LAYOUT_END) {
1615                        hasInvalidGap = !areAllEndsEqual();
1616                    } else { // layoutState.mLayoutDirection == LAYOUT_START
1617                        hasInvalidGap = !areAllStartsEqual();
1618                    }
1619                    if (hasInvalidGap) {
1620                        final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
1621                                .getFullSpanItem(position);
1622                        if (fullSpanItem != null) {
1623                            fullSpanItem.mHasUnwantedGapAfter = true;
1624                        }
1625                        mLaidOutInvalidFullSpan = true;
1626                    }
1627                }
1628            }
1629            attachViewToSpans(view, lp, layoutState);
1630            final int otherStart;
1631            final int otherEnd;
1632            if (isLayoutRTL() && mOrientation == VERTICAL) {
1633                otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() :
1634                        mSecondaryOrientation.getEndAfterPadding()
1635                                - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan;
1636                otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view);
1637            } else {
1638                otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
1639                        : currentSpan.mIndex * mSizePerSpan +
1640                                mSecondaryOrientation.getStartAfterPadding();
1641                otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
1642            }
1643
1644            if (mOrientation == VERTICAL) {
1645                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
1646            } else {
1647                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
1648            }
1649
1650            if (lp.mFullSpan) {
1651                updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
1652            } else {
1653                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
1654            }
1655            recycle(recycler, mLayoutState);
1656            if (mLayoutState.mStopInFocusable && view.isFocusable()) {
1657                if (lp.mFullSpan) {
1658                    mRemainingSpans.clear();
1659                } else {
1660                    mRemainingSpans.set(currentSpan.mIndex, false);
1661                }
1662            }
1663            added = true;
1664        }
1665        if (!added) {
1666            recycle(recycler, mLayoutState);
1667        }
1668        final int diff;
1669        if (mLayoutState.mLayoutDirection == LAYOUT_START) {
1670            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1671            diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
1672        } else {
1673            final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1674            diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
1675        }
1676        return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
1677    }
1678
1679    private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
1680        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1681        fsi.mGapPerSpan = new int[mSpanCount];
1682        for (int i = 0; i < mSpanCount; i++) {
1683            fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
1684        }
1685        return fsi;
1686    }
1687
1688    private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
1689        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1690        fsi.mGapPerSpan = new int[mSpanCount];
1691        for (int i = 0; i < mSpanCount; i++) {
1692            fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
1693        }
1694        return fsi;
1695    }
1696
1697    private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) {
1698        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1699            if (lp.mFullSpan) {
1700                appendViewToAllSpans(view);
1701            } else {
1702                lp.mSpan.appendToSpan(view);
1703            }
1704        } else {
1705            if (lp.mFullSpan) {
1706                prependViewToAllSpans(view);
1707            } else {
1708                lp.mSpan.prependToSpan(view);
1709            }
1710        }
1711    }
1712
1713    private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
1714        if (!layoutState.mRecycle || layoutState.mInfinite) {
1715            return;
1716        }
1717        if (layoutState.mAvailable == 0) {
1718            // easy, recycle line is still valid
1719            if (layoutState.mLayoutDirection == LAYOUT_START) {
1720                recycleFromEnd(recycler, layoutState.mEndLine);
1721            } else {
1722                recycleFromStart(recycler, layoutState.mStartLine);
1723            }
1724        } else {
1725            // scrolling case, recycle line can be shifted by how much space we could cover
1726            // by adding new views
1727            if (layoutState.mLayoutDirection == LAYOUT_START) {
1728                // calculate recycle line
1729                int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
1730                final int line;
1731                if (scrolled < 0) {
1732                    line = layoutState.mEndLine;
1733                } else {
1734                    line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
1735                }
1736                recycleFromEnd(recycler, line);
1737            } else {
1738                // calculate recycle line
1739                int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
1740                final int line;
1741                if (scrolled < 0) {
1742                    line = layoutState.mStartLine;
1743                } else {
1744                    line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
1745                }
1746                recycleFromStart(recycler, line);
1747            }
1748        }
1749
1750    }
1751
1752    private void appendViewToAllSpans(View view) {
1753        // traverse in reverse so that we end up assigning full span items to 0
1754        for (int i = mSpanCount - 1; i >= 0; i--) {
1755            mSpans[i].appendToSpan(view);
1756        }
1757    }
1758
1759    private void prependViewToAllSpans(View view) {
1760        // traverse in reverse so that we end up assigning full span items to 0
1761        for (int i = mSpanCount - 1; i >= 0; i--) {
1762            mSpans[i].prependToSpan(view);
1763        }
1764    }
1765
1766    private void updateAllRemainingSpans(int layoutDir, int targetLine) {
1767        for (int i = 0; i < mSpanCount; i++) {
1768            if (mSpans[i].mViews.isEmpty()) {
1769                continue;
1770            }
1771            updateRemainingSpans(mSpans[i], layoutDir, targetLine);
1772        }
1773    }
1774
1775    private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
1776        final int deletedSize = span.getDeletedSize();
1777        if (layoutDir == LAYOUT_START) {
1778            final int line = span.getStartLine();
1779            if (line + deletedSize <= targetLine) {
1780                mRemainingSpans.set(span.mIndex, false);
1781            }
1782        } else {
1783            final int line = span.getEndLine();
1784            if (line - deletedSize >= targetLine) {
1785                mRemainingSpans.set(span.mIndex, false);
1786            }
1787        }
1788    }
1789
1790    private int getMaxStart(int def) {
1791        int maxStart = mSpans[0].getStartLine(def);
1792        for (int i = 1; i < mSpanCount; i++) {
1793            final int spanStart = mSpans[i].getStartLine(def);
1794            if (spanStart > maxStart) {
1795                maxStart = spanStart;
1796            }
1797        }
1798        return maxStart;
1799    }
1800
1801    private int getMinStart(int def) {
1802        int minStart = mSpans[0].getStartLine(def);
1803        for (int i = 1; i < mSpanCount; i++) {
1804            final int spanStart = mSpans[i].getStartLine(def);
1805            if (spanStart < minStart) {
1806                minStart = spanStart;
1807            }
1808        }
1809        return minStart;
1810    }
1811
1812    boolean areAllEndsEqual() {
1813        int end = mSpans[0].getEndLine(Span.INVALID_LINE);
1814        for (int i = 1; i < mSpanCount; i++) {
1815            if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
1816                return false;
1817            }
1818        }
1819        return true;
1820    }
1821
1822    boolean areAllStartsEqual() {
1823        int start = mSpans[0].getStartLine(Span.INVALID_LINE);
1824        for (int i = 1; i < mSpanCount; i++) {
1825            if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
1826                return false;
1827            }
1828        }
1829        return true;
1830    }
1831
1832    private int getMaxEnd(int def) {
1833        int maxEnd = mSpans[0].getEndLine(def);
1834        for (int i = 1; i < mSpanCount; i++) {
1835            final int spanEnd = mSpans[i].getEndLine(def);
1836            if (spanEnd > maxEnd) {
1837                maxEnd = spanEnd;
1838            }
1839        }
1840        return maxEnd;
1841    }
1842
1843    private int getMinEnd(int def) {
1844        int minEnd = mSpans[0].getEndLine(def);
1845        for (int i = 1; i < mSpanCount; i++) {
1846            final int spanEnd = mSpans[i].getEndLine(def);
1847            if (spanEnd < minEnd) {
1848                minEnd = spanEnd;
1849            }
1850        }
1851        return minEnd;
1852    }
1853
1854    private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
1855        while (getChildCount() > 0) {
1856            View child = getChildAt(0);
1857            if (mPrimaryOrientation.getDecoratedEnd(child) <= line &&
1858                    mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) {
1859                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1860                // Don't recycle the last View in a span not to lose span's start/end lines
1861                if (lp.mFullSpan) {
1862                    for (int j = 0; j < mSpanCount; j++) {
1863                        if (mSpans[j].mViews.size() == 1) {
1864                            return;
1865                        }
1866                    }
1867                    for (int j = 0; j < mSpanCount; j++) {
1868                        mSpans[j].popStart();
1869                    }
1870                } else {
1871                    if (lp.mSpan.mViews.size() == 1) {
1872                        return;
1873                    }
1874                    lp.mSpan.popStart();
1875                }
1876                removeAndRecycleView(child, recycler);
1877            } else {
1878                return;// done
1879            }
1880        }
1881    }
1882
1883    private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
1884        final int childCount = getChildCount();
1885        int i;
1886        for (i = childCount - 1; i >= 0; i--) {
1887            View child = getChildAt(i);
1888            if (mPrimaryOrientation.getDecoratedStart(child) >= line &&
1889                    mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) {
1890                LayoutParams lp = (LayoutParams) child.getLayoutParams();
1891                // Don't recycle the last View in a span not to lose span's start/end lines
1892                if (lp.mFullSpan) {
1893                    for (int j = 0; j < mSpanCount; j++) {
1894                        if (mSpans[j].mViews.size() == 1) {
1895                            return;
1896                        }
1897                    }
1898                    for (int j = 0; j < mSpanCount; j++) {
1899                        mSpans[j].popEnd();
1900                    }
1901                } else {
1902                    if (lp.mSpan.mViews.size() == 1) {
1903                        return;
1904                    }
1905                    lp.mSpan.popEnd();
1906                }
1907                removeAndRecycleView(child, recycler);
1908            } else {
1909                return;// done
1910            }
1911        }
1912    }
1913
1914    /**
1915     * @return True if last span is the first one we want to fill
1916     */
1917    private boolean preferLastSpan(int layoutDir) {
1918        if (mOrientation == HORIZONTAL) {
1919            return (layoutDir == LAYOUT_START) != mShouldReverseLayout;
1920        }
1921        return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
1922    }
1923
1924    /**
1925     * Finds the span for the next view.
1926     */
1927    private Span getNextSpan(LayoutState layoutState) {
1928        final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
1929        final int startIndex, endIndex, diff;
1930        if (preferLastSpan) {
1931            startIndex = mSpanCount - 1;
1932            endIndex = -1;
1933            diff = -1;
1934        } else {
1935            startIndex = 0;
1936            endIndex = mSpanCount;
1937            diff = 1;
1938        }
1939        if (layoutState.mLayoutDirection == LAYOUT_END) {
1940            Span min = null;
1941            int minLine = Integer.MAX_VALUE;
1942            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
1943            for (int i = startIndex; i != endIndex; i += diff) {
1944                final Span other = mSpans[i];
1945                int otherLine = other.getEndLine(defaultLine);
1946                if (otherLine < minLine) {
1947                    min = other;
1948                    minLine = otherLine;
1949                }
1950            }
1951            return min;
1952        } else {
1953            Span max = null;
1954            int maxLine = Integer.MIN_VALUE;
1955            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
1956            for (int i = startIndex; i != endIndex; i += diff) {
1957                final Span other = mSpans[i];
1958                int otherLine = other.getStartLine(defaultLine);
1959                if (otherLine > maxLine) {
1960                    max = other;
1961                    maxLine = otherLine;
1962                }
1963            }
1964            return max;
1965        }
1966    }
1967
1968    @Override
1969    public boolean canScrollVertically() {
1970        return mOrientation == VERTICAL;
1971    }
1972
1973    @Override
1974    public boolean canScrollHorizontally() {
1975        return mOrientation == HORIZONTAL;
1976    }
1977
1978    @Override
1979    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
1980            RecyclerView.State state) {
1981        return scrollBy(dx, recycler, state);
1982    }
1983
1984    @Override
1985    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
1986            RecyclerView.State state) {
1987        return scrollBy(dy, recycler, state);
1988    }
1989
1990    private int calculateScrollDirectionForPosition(int position) {
1991        if (getChildCount() == 0) {
1992            return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
1993        }
1994        final int firstChildPos = getFirstChildPosition();
1995        return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
1996    }
1997
1998    @Override
1999    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
2000            int position) {
2001        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
2002            @Override
2003            public PointF computeScrollVectorForPosition(int targetPosition) {
2004                final int direction = calculateScrollDirectionForPosition(targetPosition);
2005                if (direction == 0) {
2006                    return null;
2007                }
2008                if (mOrientation == HORIZONTAL) {
2009                    return new PointF(direction, 0);
2010                } else {
2011                    return new PointF(0, direction);
2012                }
2013            }
2014        };
2015        scroller.setTargetPosition(position);
2016        startSmoothScroll(scroller);
2017    }
2018
2019    @Override
2020    public void scrollToPosition(int position) {
2021        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
2022            mPendingSavedState.invalidateAnchorPositionInfo();
2023        }
2024        mPendingScrollPosition = position;
2025        mPendingScrollPositionOffset = INVALID_OFFSET;
2026        requestLayout();
2027    }
2028
2029    /**
2030     * Scroll to the specified adapter position with the given offset from layout start.
2031     * <p>
2032     * Note that scroll position change will not be reflected until the next layout call.
2033     * <p>
2034     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
2035     *
2036     * @param position Index (starting at 0) of the reference item.
2037     * @param offset   The distance (in pixels) between the start edge of the item view and
2038     *                 start edge of the RecyclerView.
2039     * @see #setReverseLayout(boolean)
2040     * @see #scrollToPosition(int)
2041     */
2042    public void scrollToPositionWithOffset(int position, int offset) {
2043        if (mPendingSavedState != null) {
2044            mPendingSavedState.invalidateAnchorPositionInfo();
2045        }
2046        mPendingScrollPosition = position;
2047        mPendingScrollPositionOffset = offset;
2048        requestLayout();
2049    }
2050
2051    int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
2052        final int referenceChildPosition;
2053        final int layoutDir;
2054        if (dt > 0) { // layout towards end
2055            layoutDir = LAYOUT_END;
2056            referenceChildPosition = getLastChildPosition();
2057        } else {
2058            layoutDir = LAYOUT_START;
2059            referenceChildPosition = getFirstChildPosition();
2060        }
2061        mLayoutState.mRecycle = true;
2062        updateLayoutState(referenceChildPosition, state);
2063        setLayoutStateDirection(layoutDir);
2064        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
2065        final int absDt = Math.abs(dt);
2066        mLayoutState.mAvailable = absDt;
2067        int consumed = fill(recycler, mLayoutState, state);
2068        final int totalScroll;
2069        if (absDt < consumed) {
2070            totalScroll = dt;
2071        } else if (dt < 0) {
2072            totalScroll = -consumed;
2073        } else { // dt > 0
2074            totalScroll = consumed;
2075        }
2076        if (DEBUG) {
2077            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
2078        }
2079
2080        mPrimaryOrientation.offsetChildren(-totalScroll);
2081        // always reset this if we scroll for a proper save instance state
2082        mLastLayoutFromEnd = mShouldReverseLayout;
2083        return totalScroll;
2084    }
2085
2086    private int getLastChildPosition() {
2087        final int childCount = getChildCount();
2088        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
2089    }
2090
2091    private int getFirstChildPosition() {
2092        final int childCount = getChildCount();
2093        return childCount == 0 ? 0 : getPosition(getChildAt(0));
2094    }
2095
2096    /**
2097     * Finds the first View that can be used as an anchor View.
2098     *
2099     * @return Position of the View or 0 if it cannot find any such View.
2100     */
2101    private int findFirstReferenceChildPosition(int itemCount) {
2102        final int limit = getChildCount();
2103        for (int i = 0; i < limit; i++) {
2104            final View view = getChildAt(i);
2105            final int position = getPosition(view);
2106            if (position >= 0 && position < itemCount) {
2107                return position;
2108            }
2109        }
2110        return 0;
2111    }
2112
2113    /**
2114     * Finds the last View that can be used as an anchor View.
2115     *
2116     * @return Position of the View or 0 if it cannot find any such View.
2117     */
2118    private int findLastReferenceChildPosition(int itemCount) {
2119        for (int i = getChildCount() - 1; i >= 0; i--) {
2120            final View view = getChildAt(i);
2121            final int position = getPosition(view);
2122            if (position >= 0 && position < itemCount) {
2123                return position;
2124            }
2125        }
2126        return 0;
2127    }
2128
2129    @SuppressWarnings("deprecation")
2130    @Override
2131    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
2132        if (mOrientation == HORIZONTAL) {
2133            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
2134                    ViewGroup.LayoutParams.FILL_PARENT);
2135        } else {
2136            return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
2137                    ViewGroup.LayoutParams.WRAP_CONTENT);
2138        }
2139    }
2140
2141    @Override
2142    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
2143        return new LayoutParams(c, attrs);
2144    }
2145
2146    @Override
2147    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
2148        if (lp instanceof ViewGroup.MarginLayoutParams) {
2149            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
2150        } else {
2151            return new LayoutParams(lp);
2152        }
2153    }
2154
2155    @Override
2156    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
2157        return lp instanceof LayoutParams;
2158    }
2159
2160    public int getOrientation() {
2161        return mOrientation;
2162    }
2163
2164    @Nullable
2165    @Override
2166    public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
2167            RecyclerView.State state) {
2168        if (getChildCount() == 0) {
2169            return null;
2170        }
2171
2172        final View directChild = findContainingItemView(focused);
2173        if (directChild == null) {
2174            return null;
2175        }
2176
2177        resolveShouldLayoutReverse();
2178        final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
2179        if (layoutDir == LayoutState.INVALID_LAYOUT) {
2180            return null;
2181        }
2182        LayoutParams prevFocusLayoutParams = (LayoutParams) directChild.getLayoutParams();
2183        boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan;
2184        final Span prevFocusSpan = prevFocusLayoutParams.mSpan;
2185        final int referenceChildPosition;
2186        if (layoutDir == LAYOUT_END) { // layout towards end
2187            referenceChildPosition = getLastChildPosition();
2188        } else {
2189            referenceChildPosition = getFirstChildPosition();
2190        }
2191        updateLayoutState(referenceChildPosition, state);
2192        setLayoutStateDirection(layoutDir);
2193
2194        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
2195        mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace());
2196        mLayoutState.mStopInFocusable = true;
2197        mLayoutState.mRecycle = false;
2198        fill(recycler, mLayoutState, state);
2199        mLastLayoutFromEnd = mShouldReverseLayout;
2200        if (!prevFocusFullSpan) {
2201            View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir);
2202            if (view != null && view != directChild) {
2203                return view;
2204            }
2205        }
2206        // either could not find from the desired span or prev view is full span.
2207        // traverse all spans
2208        if (preferLastSpan(layoutDir)) {
2209            for (int i = mSpanCount - 1; i >= 0; i --) {
2210                View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
2211                if (view != null && view != directChild) {
2212                    return view;
2213                }
2214            }
2215        } else {
2216            for (int i = 0; i < mSpanCount; i ++) {
2217                View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
2218                if (view != null && view != directChild) {
2219                    return view;
2220                }
2221            }
2222        }
2223        return null;
2224    }
2225
2226    /**
2227     * Converts a focusDirection to orientation.
2228     *
2229     * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
2230     *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
2231     *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
2232     *                       or 0 for not applicable
2233     * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
2234     * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
2235     */
2236    private int convertFocusDirectionToLayoutDirection(int focusDirection) {
2237        switch (focusDirection) {
2238            case View.FOCUS_BACKWARD:
2239                if (mOrientation == VERTICAL) {
2240                    return LayoutState.LAYOUT_START;
2241                } else if (isLayoutRTL()) {
2242                    return LayoutState.LAYOUT_END;
2243                } else {
2244                    return LayoutState.LAYOUT_START;
2245                }
2246            case View.FOCUS_FORWARD:
2247                if (mOrientation == VERTICAL) {
2248                    return LayoutState.LAYOUT_END;
2249                } else if (isLayoutRTL()) {
2250                    return LayoutState.LAYOUT_START;
2251                } else {
2252                    return LayoutState.LAYOUT_END;
2253                }
2254            case View.FOCUS_UP:
2255                return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
2256                        : LayoutState.INVALID_LAYOUT;
2257            case View.FOCUS_DOWN:
2258                return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
2259                        : LayoutState.INVALID_LAYOUT;
2260            case View.FOCUS_LEFT:
2261                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
2262                        : LayoutState.INVALID_LAYOUT;
2263            case View.FOCUS_RIGHT:
2264                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
2265                        : LayoutState.INVALID_LAYOUT;
2266            default:
2267                if (DEBUG) {
2268                    Log.d(TAG, "Unknown focus request:" + focusDirection);
2269                }
2270                return LayoutState.INVALID_LAYOUT;
2271        }
2272
2273    }
2274
2275    /**
2276     * LayoutParams used by StaggeredGridLayoutManager.
2277     * <p>
2278     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
2279     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
2280     * expected to fill all of the space given to it.
2281     */
2282    public static class LayoutParams extends RecyclerView.LayoutParams {
2283
2284        /**
2285         * Span Id for Views that are not laid out yet.
2286         */
2287        public static final int INVALID_SPAN_ID = -1;
2288
2289        // Package scope to be able to access from tests.
2290        Span mSpan;
2291
2292        boolean mFullSpan;
2293
2294        public LayoutParams(Context c, AttributeSet attrs) {
2295            super(c, attrs);
2296        }
2297
2298        public LayoutParams(int width, int height) {
2299            super(width, height);
2300        }
2301
2302        public LayoutParams(ViewGroup.MarginLayoutParams source) {
2303            super(source);
2304        }
2305
2306        public LayoutParams(ViewGroup.LayoutParams source) {
2307            super(source);
2308        }
2309
2310        public LayoutParams(RecyclerView.LayoutParams source) {
2311            super(source);
2312        }
2313
2314        /**
2315         * When set to true, the item will layout using all span area. That means, if orientation
2316         * is vertical, the view will have full width; if orientation is horizontal, the view will
2317         * have full height.
2318         *
2319         * @param fullSpan True if this item should traverse all spans.
2320         * @see #isFullSpan()
2321         */
2322        public void setFullSpan(boolean fullSpan) {
2323            mFullSpan = fullSpan;
2324        }
2325
2326        /**
2327         * Returns whether this View occupies all available spans or just one.
2328         *
2329         * @return True if the View occupies all spans or false otherwise.
2330         * @see #setFullSpan(boolean)
2331         */
2332        public boolean isFullSpan() {
2333            return mFullSpan;
2334        }
2335
2336        /**
2337         * Returns the Span index to which this View is assigned.
2338         *
2339         * @return The Span index of the View. If View is not yet assigned to any span, returns
2340         * {@link #INVALID_SPAN_ID}.
2341         */
2342        public final int getSpanIndex() {
2343            if (mSpan == null) {
2344                return INVALID_SPAN_ID;
2345            }
2346            return mSpan.mIndex;
2347        }
2348    }
2349
2350    // Package scoped to access from tests.
2351    class Span {
2352
2353        static final int INVALID_LINE = Integer.MIN_VALUE;
2354        private ArrayList<View> mViews = new ArrayList<>();
2355        int mCachedStart = INVALID_LINE;
2356        int mCachedEnd = INVALID_LINE;
2357        int mDeletedSize = 0;
2358        final int mIndex;
2359
2360        private Span(int index) {
2361            mIndex = index;
2362        }
2363
2364        int getStartLine(int def) {
2365            if (mCachedStart != INVALID_LINE) {
2366                return mCachedStart;
2367            }
2368            if (mViews.size() == 0) {
2369                return def;
2370            }
2371            calculateCachedStart();
2372            return mCachedStart;
2373        }
2374
2375        void calculateCachedStart() {
2376            final View startView = mViews.get(0);
2377            final LayoutParams lp = getLayoutParams(startView);
2378            mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
2379            if (lp.mFullSpan) {
2380                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2381                        .getFullSpanItem(lp.getViewLayoutPosition());
2382                if (fsi != null && fsi.mGapDir == LAYOUT_START) {
2383                    mCachedStart -= fsi.getGapForSpan(mIndex);
2384                }
2385            }
2386        }
2387
2388        // Use this one when default value does not make sense and not having a value means a bug.
2389        int getStartLine() {
2390            if (mCachedStart != INVALID_LINE) {
2391                return mCachedStart;
2392            }
2393            calculateCachedStart();
2394            return mCachedStart;
2395        }
2396
2397        int getEndLine(int def) {
2398            if (mCachedEnd != INVALID_LINE) {
2399                return mCachedEnd;
2400            }
2401            final int size = mViews.size();
2402            if (size == 0) {
2403                return def;
2404            }
2405            calculateCachedEnd();
2406            return mCachedEnd;
2407        }
2408
2409        void calculateCachedEnd() {
2410            final View endView = mViews.get(mViews.size() - 1);
2411            final LayoutParams lp = getLayoutParams(endView);
2412            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
2413            if (lp.mFullSpan) {
2414                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2415                        .getFullSpanItem(lp.getViewLayoutPosition());
2416                if (fsi != null && fsi.mGapDir == LAYOUT_END) {
2417                    mCachedEnd += fsi.getGapForSpan(mIndex);
2418                }
2419            }
2420        }
2421
2422        // Use this one when default value does not make sense and not having a value means a bug.
2423        int getEndLine() {
2424            if (mCachedEnd != INVALID_LINE) {
2425                return mCachedEnd;
2426            }
2427            calculateCachedEnd();
2428            return mCachedEnd;
2429        }
2430
2431        void prependToSpan(View view) {
2432            LayoutParams lp = getLayoutParams(view);
2433            lp.mSpan = this;
2434            mViews.add(0, view);
2435            mCachedStart = INVALID_LINE;
2436            if (mViews.size() == 1) {
2437                mCachedEnd = INVALID_LINE;
2438            }
2439            if (lp.isItemRemoved() || lp.isItemChanged()) {
2440                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2441            }
2442        }
2443
2444        void appendToSpan(View view) {
2445            LayoutParams lp = getLayoutParams(view);
2446            lp.mSpan = this;
2447            mViews.add(view);
2448            mCachedEnd = INVALID_LINE;
2449            if (mViews.size() == 1) {
2450                mCachedStart = INVALID_LINE;
2451            }
2452            if (lp.isItemRemoved() || lp.isItemChanged()) {
2453                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2454            }
2455        }
2456
2457        // Useful method to preserve positions on a re-layout.
2458        void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
2459            int reference;
2460            if (reverseLayout) {
2461                reference = getEndLine(INVALID_LINE);
2462            } else {
2463                reference = getStartLine(INVALID_LINE);
2464            }
2465            clear();
2466            if (reference == INVALID_LINE) {
2467                return;
2468            }
2469            if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
2470                    (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
2471                return;
2472            }
2473            if (offset != INVALID_OFFSET) {
2474                reference += offset;
2475            }
2476            mCachedStart = mCachedEnd = reference;
2477        }
2478
2479        void clear() {
2480            mViews.clear();
2481            invalidateCache();
2482            mDeletedSize = 0;
2483        }
2484
2485        void invalidateCache() {
2486            mCachedStart = INVALID_LINE;
2487            mCachedEnd = INVALID_LINE;
2488        }
2489
2490        void setLine(int line) {
2491            mCachedEnd = mCachedStart = line;
2492        }
2493
2494        void popEnd() {
2495            final int size = mViews.size();
2496            View end = mViews.remove(size - 1);
2497            final LayoutParams lp = getLayoutParams(end);
2498            lp.mSpan = null;
2499            if (lp.isItemRemoved() || lp.isItemChanged()) {
2500                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
2501            }
2502            if (size == 1) {
2503                mCachedStart = INVALID_LINE;
2504            }
2505            mCachedEnd = INVALID_LINE;
2506        }
2507
2508        void popStart() {
2509            View start = mViews.remove(0);
2510            final LayoutParams lp = getLayoutParams(start);
2511            lp.mSpan = null;
2512            if (mViews.size() == 0) {
2513                mCachedEnd = INVALID_LINE;
2514            }
2515            if (lp.isItemRemoved() || lp.isItemChanged()) {
2516                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
2517            }
2518            mCachedStart = INVALID_LINE;
2519        }
2520
2521        public int getDeletedSize() {
2522            return mDeletedSize;
2523        }
2524
2525        LayoutParams getLayoutParams(View view) {
2526            return (LayoutParams) view.getLayoutParams();
2527        }
2528
2529        void onOffset(int dt) {
2530            if (mCachedStart != INVALID_LINE) {
2531                mCachedStart += dt;
2532            }
2533            if (mCachedEnd != INVALID_LINE) {
2534                mCachedEnd += dt;
2535            }
2536        }
2537
2538        public int findFirstVisibleItemPosition() {
2539            return mReverseLayout
2540                    ? findOneVisibleChild(mViews.size() - 1, -1, false)
2541                    : findOneVisibleChild(0, mViews.size(), false);
2542        }
2543
2544        public int findFirstCompletelyVisibleItemPosition() {
2545            return mReverseLayout
2546                    ? findOneVisibleChild(mViews.size() - 1, -1, true)
2547                    : findOneVisibleChild(0, mViews.size(), true);
2548        }
2549
2550        public int findLastVisibleItemPosition() {
2551            return mReverseLayout
2552                    ? findOneVisibleChild(0, mViews.size(), false)
2553                    : findOneVisibleChild(mViews.size() - 1, -1, false);
2554        }
2555
2556        public int findLastCompletelyVisibleItemPosition() {
2557            return mReverseLayout
2558                    ? findOneVisibleChild(0, mViews.size(), true)
2559                    : findOneVisibleChild(mViews.size() - 1, -1, true);
2560        }
2561
2562        int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
2563            final int start = mPrimaryOrientation.getStartAfterPadding();
2564            final int end = mPrimaryOrientation.getEndAfterPadding();
2565            final int next = toIndex > fromIndex ? 1 : -1;
2566            for (int i = fromIndex; i != toIndex; i += next) {
2567                final View child = mViews.get(i);
2568                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
2569                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
2570                if (childStart < end && childEnd > start) {
2571                    if (completelyVisible) {
2572                        if (childStart >= start && childEnd <= end) {
2573                            return getPosition(child);
2574                        }
2575                    } else {
2576                        return getPosition(child);
2577                    }
2578                }
2579            }
2580            return NO_POSITION;
2581        }
2582
2583        /**
2584         * Depending on the layout direction, returns the View that is after the given position.
2585         */
2586        public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) {
2587            View candidate = null;
2588            if (layoutDir == LAYOUT_START) {
2589                final int limit = mViews.size();
2590                for (int i = 0; i < limit; i++) {
2591                    final View view = mViews.get(i);
2592                    if (view.isFocusable() &&
2593                            (getPosition(view) > referenceChildPosition == mReverseLayout) ) {
2594                        candidate = view;
2595                    } else {
2596                        break;
2597                    }
2598                }
2599            } else {
2600                for (int i = mViews.size() - 1; i >= 0; i--) {
2601                    final View view = mViews.get(i);
2602                    if (view.isFocusable() &&
2603                            (getPosition(view) > referenceChildPosition == !mReverseLayout)) {
2604                        candidate = view;
2605                    } else {
2606                        break;
2607                    }
2608                }
2609            }
2610            return candidate;
2611        }
2612    }
2613
2614    /**
2615     * An array of mappings from adapter position to span.
2616     * This only grows when a write happens and it grows up to the size of the adapter.
2617     */
2618    static class LazySpanLookup {
2619
2620        private static final int MIN_SIZE = 10;
2621        int[] mData;
2622        List<FullSpanItem> mFullSpanItems;
2623
2624
2625        /**
2626         * Invalidates everything after this position, including full span information
2627         */
2628        int forceInvalidateAfter(int position) {
2629            if (mFullSpanItems != null) {
2630                for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2631                    FullSpanItem fsi = mFullSpanItems.get(i);
2632                    if (fsi.mPosition >= position) {
2633                        mFullSpanItems.remove(i);
2634                    }
2635                }
2636            }
2637            return invalidateAfter(position);
2638        }
2639
2640        /**
2641         * returns end position for invalidation.
2642         */
2643        int invalidateAfter(int position) {
2644            if (mData == null) {
2645                return RecyclerView.NO_POSITION;
2646            }
2647            if (position >= mData.length) {
2648                return RecyclerView.NO_POSITION;
2649            }
2650            int endPosition = invalidateFullSpansAfter(position);
2651            if (endPosition == RecyclerView.NO_POSITION) {
2652                Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
2653                return mData.length;
2654            } else {
2655                // just invalidate items in between
2656                Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID);
2657                return endPosition + 1;
2658            }
2659        }
2660
2661        int getSpan(int position) {
2662            if (mData == null || position >= mData.length) {
2663                return LayoutParams.INVALID_SPAN_ID;
2664            } else {
2665                return mData[position];
2666            }
2667        }
2668
2669        void setSpan(int position, Span span) {
2670            ensureSize(position);
2671            mData[position] = span.mIndex;
2672        }
2673
2674        int sizeForPosition(int position) {
2675            int len = mData.length;
2676            while (len <= position) {
2677                len *= 2;
2678            }
2679            return len;
2680        }
2681
2682        void ensureSize(int position) {
2683            if (mData == null) {
2684                mData = new int[Math.max(position, MIN_SIZE) + 1];
2685                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2686            } else if (position >= mData.length) {
2687                int[] old = mData;
2688                mData = new int[sizeForPosition(position)];
2689                System.arraycopy(old, 0, mData, 0, old.length);
2690                Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
2691            }
2692        }
2693
2694        void clear() {
2695            if (mData != null) {
2696                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2697            }
2698            mFullSpanItems = null;
2699        }
2700
2701        void offsetForRemoval(int positionStart, int itemCount) {
2702            if (mData == null || positionStart >= mData.length) {
2703                return;
2704            }
2705            ensureSize(positionStart + itemCount);
2706            System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
2707                    mData.length - positionStart - itemCount);
2708            Arrays.fill(mData, mData.length - itemCount, mData.length,
2709                    LayoutParams.INVALID_SPAN_ID);
2710            offsetFullSpansForRemoval(positionStart, itemCount);
2711        }
2712
2713        private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
2714            if (mFullSpanItems == null) {
2715                return;
2716            }
2717            final int end = positionStart + itemCount;
2718            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2719                FullSpanItem fsi = mFullSpanItems.get(i);
2720                if (fsi.mPosition < positionStart) {
2721                    continue;
2722                }
2723                if (fsi.mPosition < end) {
2724                    mFullSpanItems.remove(i);
2725                } else {
2726                    fsi.mPosition -= itemCount;
2727                }
2728            }
2729        }
2730
2731        void offsetForAddition(int positionStart, int itemCount) {
2732            if (mData == null || positionStart >= mData.length) {
2733                return;
2734            }
2735            ensureSize(positionStart + itemCount);
2736            System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
2737                    mData.length - positionStart - itemCount);
2738            Arrays.fill(mData, positionStart, positionStart + itemCount,
2739                    LayoutParams.INVALID_SPAN_ID);
2740            offsetFullSpansForAddition(positionStart, itemCount);
2741        }
2742
2743        private void offsetFullSpansForAddition(int positionStart, int itemCount) {
2744            if (mFullSpanItems == null) {
2745                return;
2746            }
2747            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2748                FullSpanItem fsi = mFullSpanItems.get(i);
2749                if (fsi.mPosition < positionStart) {
2750                    continue;
2751                }
2752                fsi.mPosition += itemCount;
2753            }
2754        }
2755
2756        /**
2757         * Returns when invalidation should end. e.g. hitting a full span position.
2758         * Returned position SHOULD BE invalidated.
2759         */
2760        private int invalidateFullSpansAfter(int position) {
2761            if (mFullSpanItems == null) {
2762                return RecyclerView.NO_POSITION;
2763            }
2764            final FullSpanItem item = getFullSpanItem(position);
2765            // if there is an fsi at this position, get rid of it.
2766            if (item != null) {
2767                mFullSpanItems.remove(item);
2768            }
2769            int nextFsiIndex = -1;
2770            final int count = mFullSpanItems.size();
2771            for (int i = 0; i < count; i++) {
2772                FullSpanItem fsi = mFullSpanItems.get(i);
2773                if (fsi.mPosition >= position) {
2774                    nextFsiIndex = i;
2775                    break;
2776                }
2777            }
2778            if (nextFsiIndex != -1) {
2779                FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
2780                mFullSpanItems.remove(nextFsiIndex);
2781                return fsi.mPosition;
2782            }
2783            return RecyclerView.NO_POSITION;
2784        }
2785
2786        public void addFullSpanItem(FullSpanItem fullSpanItem) {
2787            if (mFullSpanItems == null) {
2788                mFullSpanItems = new ArrayList<>();
2789            }
2790            final int size = mFullSpanItems.size();
2791            for (int i = 0; i < size; i++) {
2792                FullSpanItem other = mFullSpanItems.get(i);
2793                if (other.mPosition == fullSpanItem.mPosition) {
2794                    if (DEBUG) {
2795                        throw new IllegalStateException("two fsis for same position");
2796                    } else {
2797                        mFullSpanItems.remove(i);
2798                    }
2799                }
2800                if (other.mPosition >= fullSpanItem.mPosition) {
2801                    mFullSpanItems.add(i, fullSpanItem);
2802                    return;
2803                }
2804            }
2805            // if it is not added to a position.
2806            mFullSpanItems.add(fullSpanItem);
2807        }
2808
2809        public FullSpanItem getFullSpanItem(int position) {
2810            if (mFullSpanItems == null) {
2811                return null;
2812            }
2813            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2814                final FullSpanItem fsi = mFullSpanItems.get(i);
2815                if (fsi.mPosition == position) {
2816                    return fsi;
2817                }
2818            }
2819            return null;
2820        }
2821
2822        /**
2823         * @param minPos inclusive
2824         * @param maxPos exclusive
2825         * @param gapDir if not 0, returns FSIs on in that direction
2826         * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
2827         *                        returned even if its gap direction does not match.
2828         */
2829        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
2830                boolean hasUnwantedGapAfter) {
2831            if (mFullSpanItems == null) {
2832                return null;
2833            }
2834            final int limit = mFullSpanItems.size();
2835            for (int i = 0; i < limit; i++) {
2836                FullSpanItem fsi = mFullSpanItems.get(i);
2837                if (fsi.mPosition >= maxPos) {
2838                    return null;
2839                }
2840                if (fsi.mPosition >= minPos
2841                        && (gapDir == 0 || fsi.mGapDir == gapDir ||
2842                        (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
2843                    return fsi;
2844                }
2845            }
2846            return null;
2847        }
2848
2849        /**
2850         * We keep information about full span items because they may create gaps in the UI.
2851         */
2852        static class FullSpanItem implements Parcelable {
2853
2854            int mPosition;
2855            int mGapDir;
2856            int[] mGapPerSpan;
2857            // A full span may be laid out in primary direction but may have gaps due to
2858            // invalidation of views after it. This is recorded during a reverse scroll and if
2859            // view is still on the screen after scroll stops, we have to recalculate layout
2860            boolean mHasUnwantedGapAfter;
2861
2862            public FullSpanItem(Parcel in) {
2863                mPosition = in.readInt();
2864                mGapDir = in.readInt();
2865                mHasUnwantedGapAfter = in.readInt() == 1;
2866                int spanCount = in.readInt();
2867                if (spanCount > 0) {
2868                    mGapPerSpan = new int[spanCount];
2869                    in.readIntArray(mGapPerSpan);
2870                }
2871            }
2872
2873            public FullSpanItem() {
2874            }
2875
2876            int getGapForSpan(int spanIndex) {
2877                return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
2878            }
2879
2880            @Override
2881            public int describeContents() {
2882                return 0;
2883            }
2884
2885            @Override
2886            public void writeToParcel(Parcel dest, int flags) {
2887                dest.writeInt(mPosition);
2888                dest.writeInt(mGapDir);
2889                dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
2890                if (mGapPerSpan != null && mGapPerSpan.length > 0) {
2891                    dest.writeInt(mGapPerSpan.length);
2892                    dest.writeIntArray(mGapPerSpan);
2893                } else {
2894                    dest.writeInt(0);
2895                }
2896            }
2897
2898            @Override
2899            public String toString() {
2900                return "FullSpanItem{" +
2901                        "mPosition=" + mPosition +
2902                        ", mGapDir=" + mGapDir +
2903                        ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter +
2904                        ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
2905                        '}';
2906            }
2907
2908            public static final Parcelable.Creator<FullSpanItem> CREATOR
2909                    = new Parcelable.Creator<FullSpanItem>() {
2910                @Override
2911                public FullSpanItem createFromParcel(Parcel in) {
2912                    return new FullSpanItem(in);
2913                }
2914
2915                @Override
2916                public FullSpanItem[] newArray(int size) {
2917                    return new FullSpanItem[size];
2918                }
2919            };
2920        }
2921    }
2922
2923    /**
2924     * @hide
2925     */
2926    public static class SavedState implements Parcelable {
2927
2928        int mAnchorPosition;
2929        int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
2930        int mSpanOffsetsSize;
2931        int[] mSpanOffsets;
2932        int mSpanLookupSize;
2933        int[] mSpanLookup;
2934        List<LazySpanLookup.FullSpanItem> mFullSpanItems;
2935        boolean mReverseLayout;
2936        boolean mAnchorLayoutFromEnd;
2937        boolean mLastLayoutRTL;
2938
2939        public SavedState() {
2940        }
2941
2942        SavedState(Parcel in) {
2943            mAnchorPosition = in.readInt();
2944            mVisibleAnchorPosition = in.readInt();
2945            mSpanOffsetsSize = in.readInt();
2946            if (mSpanOffsetsSize > 0) {
2947                mSpanOffsets = new int[mSpanOffsetsSize];
2948                in.readIntArray(mSpanOffsets);
2949            }
2950
2951            mSpanLookupSize = in.readInt();
2952            if (mSpanLookupSize > 0) {
2953                mSpanLookup = new int[mSpanLookupSize];
2954                in.readIntArray(mSpanLookup);
2955            }
2956            mReverseLayout = in.readInt() == 1;
2957            mAnchorLayoutFromEnd = in.readInt() == 1;
2958            mLastLayoutRTL = in.readInt() == 1;
2959            //noinspection unchecked
2960            mFullSpanItems = in.readArrayList(
2961                    LazySpanLookup.FullSpanItem.class.getClassLoader());
2962        }
2963
2964        public SavedState(SavedState other) {
2965            mSpanOffsetsSize = other.mSpanOffsetsSize;
2966            mAnchorPosition = other.mAnchorPosition;
2967            mVisibleAnchorPosition = other.mVisibleAnchorPosition;
2968            mSpanOffsets = other.mSpanOffsets;
2969            mSpanLookupSize = other.mSpanLookupSize;
2970            mSpanLookup = other.mSpanLookup;
2971            mReverseLayout = other.mReverseLayout;
2972            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
2973            mLastLayoutRTL = other.mLastLayoutRTL;
2974            mFullSpanItems = other.mFullSpanItems;
2975        }
2976
2977        void invalidateSpanInfo() {
2978            mSpanOffsets = null;
2979            mSpanOffsetsSize = 0;
2980            mSpanLookupSize = 0;
2981            mSpanLookup = null;
2982            mFullSpanItems = null;
2983        }
2984
2985        void invalidateAnchorPositionInfo() {
2986            mSpanOffsets = null;
2987            mSpanOffsetsSize = 0;
2988            mAnchorPosition = NO_POSITION;
2989            mVisibleAnchorPosition = NO_POSITION;
2990        }
2991
2992        @Override
2993        public int describeContents() {
2994            return 0;
2995        }
2996
2997        @Override
2998        public void writeToParcel(Parcel dest, int flags) {
2999            dest.writeInt(mAnchorPosition);
3000            dest.writeInt(mVisibleAnchorPosition);
3001            dest.writeInt(mSpanOffsetsSize);
3002            if (mSpanOffsetsSize > 0) {
3003                dest.writeIntArray(mSpanOffsets);
3004            }
3005            dest.writeInt(mSpanLookupSize);
3006            if (mSpanLookupSize > 0) {
3007                dest.writeIntArray(mSpanLookup);
3008            }
3009            dest.writeInt(mReverseLayout ? 1 : 0);
3010            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
3011            dest.writeInt(mLastLayoutRTL ? 1 : 0);
3012            dest.writeList(mFullSpanItems);
3013        }
3014
3015        public static final Parcelable.Creator<SavedState> CREATOR
3016                = new Parcelable.Creator<SavedState>() {
3017            @Override
3018            public SavedState createFromParcel(Parcel in) {
3019                return new SavedState(in);
3020            }
3021
3022            @Override
3023            public SavedState[] newArray(int size) {
3024                return new SavedState[size];
3025            }
3026        };
3027    }
3028
3029    /**
3030     * Data class to hold the information about an anchor position which is used in onLayout call.
3031     */
3032    class AnchorInfo {
3033
3034        int mPosition;
3035        int mOffset;
3036        boolean mLayoutFromEnd;
3037        boolean mInvalidateOffsets;
3038        boolean mValid;
3039
3040        public AnchorInfo() {
3041            reset();
3042        }
3043
3044        void reset() {
3045            mPosition = NO_POSITION;
3046            mOffset = INVALID_OFFSET;
3047            mLayoutFromEnd = false;
3048            mInvalidateOffsets = false;
3049            mValid = false;
3050        }
3051
3052        void assignCoordinateFromPadding() {
3053            mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
3054                    : mPrimaryOrientation.getStartAfterPadding();
3055        }
3056
3057        void assignCoordinateFromPadding(int addedDistance) {
3058            if (mLayoutFromEnd) {
3059                mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
3060            } else {
3061                mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;
3062            }
3063        }
3064    }
3065}
3066