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