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