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