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