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