Roll android_tools support library to 25.1.0
[android_tools.git] / sdk / sources / android-23 / android / support / v7 / widget / StaggeredGridLayoutManager.java
blob322fe3450c3c9b145d34169f84a1373147a2b3f0
1 /*
2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.support.v7.widget;
19 import android.content.Context;
20 import android.graphics.PointF;
21 import android.graphics.Rect;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.support.v4.view.ViewCompat;
25 import android.support.v4.view.accessibility.AccessibilityEventCompat;
26 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
27 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.BitSet;
37 import java.util.List;
39 import static android.support.v7.widget.LayoutState.LAYOUT_START;
40 import static android.support.v7.widget.LayoutState.LAYOUT_END;
41 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
42 import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
43 import static android.support.v7.widget.RecyclerView.NO_POSITION;
45 /**
46 * A LayoutManager that lays out children in a staggered grid formation.
47 * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
48 * <p>
49 * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
50 * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
51 * control this behavior via {@link #setGapStrategy(int)}.
53 public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
55 public static final String TAG = "StaggeredGridLayoutManager";
57 private static final boolean DEBUG = false;
59 public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
61 public static final int VERTICAL = OrientationHelper.VERTICAL;
63 /**
64 * Does not do anything to hide gaps.
66 public static final int GAP_HANDLING_NONE = 0;
68 @Deprecated
69 public static final int GAP_HANDLING_LAZY = 1;
71 /**
72 * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
73 * check if there are gaps in the because of full span items. If it finds, it will re-layout
74 * and move items to correct positions with animations.
75 * <p>
76 * For example, if LayoutManager ends up with the following layout due to adapter changes:
77 * <pre>
78 * AAA
79 * _BC
80 * DDD
81 * </pre>
82 * <p>
83 * It will animate to the following state:
84 * <pre>
85 * AAA
86 * BC_
87 * DDD
88 * </pre>
90 public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
92 private static final int INVALID_OFFSET = Integer.MIN_VALUE;
94 /**
95 * Number of spans
97 private int mSpanCount = -1;
99 private Span[] mSpans;
102 * Primary orientation is the layout's orientation, secondary orientation is the orientation
103 * for spans. Having both makes code much cleaner for calculations.
105 OrientationHelper mPrimaryOrientation;
106 OrientationHelper mSecondaryOrientation;
108 private int mOrientation;
111 * The width or height per span, depending on the orientation.
113 private int mSizePerSpan;
115 private LayoutState mLayoutState;
117 private boolean mReverseLayout = false;
120 * Aggregated reverse layout value that takes RTL into account.
122 boolean mShouldReverseLayout = false;
125 * Temporary variable used during fill method to check which spans needs to be filled.
127 private BitSet mRemainingSpans;
130 * When LayoutManager needs to scroll to a position, it sets this variable and requests a
131 * layout which will check this variable and re-layout accordingly.
133 int mPendingScrollPosition = NO_POSITION;
136 * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
137 * called.
139 int mPendingScrollPositionOffset = INVALID_OFFSET;
142 * Keeps the mapping between the adapter positions and spans. This is necessary to provide
143 * a consistent experience when user scrolls the list.
145 LazySpanLookup mLazySpanLookup = new LazySpanLookup();
148 * how we handle gaps in UI.
150 private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
153 * Saved state needs this information to properly layout on restore.
155 private boolean mLastLayoutFromEnd;
158 * Saved state and onLayout needs this information to re-layout properly
160 private boolean mLastLayoutRTL;
163 * SavedState is not handled until a layout happens. This is where we keep it until next
164 * layout.
166 private SavedState mPendingSavedState;
169 * Re-used measurement specs. updated by onLayout.
171 private int mFullSizeSpec, mWidthSpec, mHeightSpec;
174 * Re-used rectangle to get child decor offsets.
176 private final Rect mTmpRect = new Rect();
179 * Re-used anchor info.
181 private final AnchorInfo mAnchorInfo = new AnchorInfo();
184 * If a full span item is invalid / or created in reverse direction; it may create gaps in
185 * the UI. While laying out, if such case is detected, we set this flag.
186 * <p>
187 * After scrolling stops, we check this flag and if it is set, re-layout.
189 private boolean mLaidOutInvalidFullSpan = false;
192 * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
193 * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
195 private boolean mSmoothScrollbarEnabled = true;
197 private final Runnable mCheckForGapsRunnable = new Runnable() {
198 @Override
199 public void run() {
200 checkForGaps();
205 * Constructor used when layout manager is set in XML by RecyclerView attribute
206 * "layoutManager". Defaults to single column and vertical.
208 public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
209 int defStyleRes) {
210 Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
211 setOrientation(properties.orientation);
212 setSpanCount(properties.spanCount);
213 setReverseLayout(properties.reverseLayout);
217 * Creates a StaggeredGridLayoutManager with given parameters.
219 * @param spanCount If orientation is vertical, spanCount is number of columns. If
220 * orientation is horizontal, spanCount is number of rows.
221 * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
223 public StaggeredGridLayoutManager(int spanCount, int orientation) {
224 mOrientation = orientation;
225 setSpanCount(spanCount);
229 * Checks for gaps in the UI that may be caused by adapter changes.
230 * <p>
231 * When a full span item is laid out in reverse direction, it sets a flag which we check when
232 * scroll is stopped (or re-layout happens) and re-layout after first valid item.
234 private boolean checkForGaps() {
235 if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) {
236 return false;
238 final int minPos, maxPos;
239 if (mShouldReverseLayout) {
240 minPos = getLastChildPosition();
241 maxPos = getFirstChildPosition();
242 } else {
243 minPos = getFirstChildPosition();
244 maxPos = getLastChildPosition();
246 if (minPos == 0) {
247 View gapView = hasGapsToFix();
248 if (gapView != null) {
249 mLazySpanLookup.clear();
250 requestSimpleAnimationsInNextLayout();
251 requestLayout();
252 return true;
255 if (!mLaidOutInvalidFullSpan) {
256 return false;
258 int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
259 final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
260 .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
261 if (invalidFsi == null) {
262 mLaidOutInvalidFullSpan = false;
263 mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
264 return false;
266 final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
267 .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
268 invalidGapDir * -1, true);
269 if (validFsi == null) {
270 mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
271 } else {
272 mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
274 requestSimpleAnimationsInNextLayout();
275 requestLayout();
276 return true;
279 @Override
280 public void onScrollStateChanged(int state) {
281 if (state == RecyclerView.SCROLL_STATE_IDLE) {
282 checkForGaps();
286 @Override
287 public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
288 removeCallbacks(mCheckForGapsRunnable);
289 for (int i = 0; i < mSpanCount; i++) {
290 mSpans[i].clear();
295 * Checks for gaps if we've reached to the top of the list.
296 * <p>
297 * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
299 View hasGapsToFix() {
300 int startChildIndex = 0;
301 int endChildIndex = getChildCount() - 1;
302 BitSet mSpansToCheck = new BitSet(mSpanCount);
303 mSpansToCheck.set(0, mSpanCount, true);
305 final int firstChildIndex, childLimit;
306 final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
308 if (mShouldReverseLayout) {
309 firstChildIndex = endChildIndex;
310 childLimit = startChildIndex - 1;
311 } else {
312 firstChildIndex = startChildIndex;
313 childLimit = endChildIndex + 1;
315 final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
316 for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
317 View child = getChildAt(i);
318 LayoutParams lp = (LayoutParams) child.getLayoutParams();
319 if (mSpansToCheck.get(lp.mSpan.mIndex)) {
320 if (checkSpanForGap(lp.mSpan)) {
321 return child;
323 mSpansToCheck.clear(lp.mSpan.mIndex);
325 if (lp.mFullSpan) {
326 continue; // quick reject
329 if (i + nextChildDiff != childLimit) {
330 View nextChild = getChildAt(i + nextChildDiff);
331 boolean compareSpans = false;
332 if (mShouldReverseLayout) {
333 // ensure child's end is below nextChild's end
334 int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
335 int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
336 if (myEnd < nextEnd) {
337 return child;//i should have a better position
338 } else if (myEnd == nextEnd) {
339 compareSpans = true;
341 } else {
342 int myStart = mPrimaryOrientation.getDecoratedStart(child);
343 int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
344 if (myStart > nextStart) {
345 return child;//i should have a better position
346 } else if (myStart == nextStart) {
347 compareSpans = true;
350 if (compareSpans) {
351 // equal, check span indices.
352 LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
353 if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
354 return child;
359 // everything looks good
360 return null;
363 private boolean checkSpanForGap(Span span) {
364 if (mShouldReverseLayout) {
365 if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
366 return true;
368 } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
369 return true;
371 return false;
375 * Sets the number of spans for the layout. This will invalidate all of the span assignments
376 * for Views.
377 * <p>
378 * Calling this method will automatically result in a new layout request unless the spanCount
379 * parameter is equal to current span count.
381 * @param spanCount Number of spans to layout
383 public void setSpanCount(int spanCount) {
384 assertNotInLayoutOrScroll(null);
385 if (spanCount != mSpanCount) {
386 invalidateSpanAssignments();
387 mSpanCount = spanCount;
388 mRemainingSpans = new BitSet(mSpanCount);
389 mSpans = new Span[mSpanCount];
390 for (int i = 0; i < mSpanCount; i++) {
391 mSpans[i] = new Span(i);
393 requestLayout();
398 * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
399 * scroll position if this method is called after views are laid out.
401 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
403 public void setOrientation(int orientation) {
404 if (orientation != HORIZONTAL && orientation != VERTICAL) {
405 throw new IllegalArgumentException("invalid orientation.");
407 assertNotInLayoutOrScroll(null);
408 if (orientation == mOrientation) {
409 return;
411 mOrientation = orientation;
412 if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
413 // swap
414 OrientationHelper tmp = mPrimaryOrientation;
415 mPrimaryOrientation = mSecondaryOrientation;
416 mSecondaryOrientation = tmp;
418 requestLayout();
422 * Sets whether LayoutManager should start laying out items from the end of the UI. The order
423 * items are traversed is not affected by this call.
424 * <p>
425 * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
426 * the list.
427 * <p>
428 * For horizontal layouts, it depends on the layout direction.
429 * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
430 * {@link RecyclerView}} is RTL, it will layout from LTR.
432 * @param reverseLayout Whether layout should be in reverse or not
434 public void setReverseLayout(boolean reverseLayout) {
435 assertNotInLayoutOrScroll(null);
436 if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
437 mPendingSavedState.mReverseLayout = reverseLayout;
439 mReverseLayout = reverseLayout;
440 requestLayout();
444 * Returns the current gap handling strategy for StaggeredGridLayoutManager.
445 * <p>
446 * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
447 * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
448 * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
449 * <p>
450 * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
452 * @return Current gap handling strategy.
453 * @see #setGapStrategy(int)
454 * @see #GAP_HANDLING_NONE
455 * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
457 public int getGapStrategy() {
458 return mGapStrategy;
462 * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
463 * is different than the current strategy, calling this method will trigger a layout request.
465 * @param gapStrategy The new gap handling strategy. Should be
466 * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
467 * #GAP_HANDLING_NONE}.
468 * @see #getGapStrategy()
470 public void setGapStrategy(int gapStrategy) {
471 assertNotInLayoutOrScroll(null);
472 if (gapStrategy == mGapStrategy) {
473 return;
475 if (gapStrategy != GAP_HANDLING_NONE &&
476 gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
477 throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
478 + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
480 mGapStrategy = gapStrategy;
481 requestLayout();
484 @Override
485 public void assertNotInLayoutOrScroll(String message) {
486 if (mPendingSavedState == null) {
487 super.assertNotInLayoutOrScroll(message);
492 * Returns the number of spans laid out by StaggeredGridLayoutManager.
494 * @return Number of spans in the layout
496 public int getSpanCount() {
497 return mSpanCount;
501 * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
502 * <p>
503 * If you need to cancel current assignments, you can call this method which will clear all
504 * assignments and request a new layout.
506 public void invalidateSpanAssignments() {
507 mLazySpanLookup.clear();
508 requestLayout();
511 private void ensureOrientationHelper() {
512 if (mPrimaryOrientation == null) {
513 mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
514 mSecondaryOrientation = OrientationHelper
515 .createOrientationHelper(this, 1 - mOrientation);
516 mLayoutState = new LayoutState();
521 * Calculates the views' layout order. (e.g. from end to start or start to end)
522 * RTL layout support is applied automatically. So if layout is RTL and
523 * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
525 private void resolveShouldLayoutReverse() {
526 // A == B is the same result, but we rather keep it readable
527 if (mOrientation == VERTICAL || !isLayoutRTL()) {
528 mShouldReverseLayout = mReverseLayout;
529 } else {
530 mShouldReverseLayout = !mReverseLayout;
534 boolean isLayoutRTL() {
535 return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
539 * Returns whether views are laid out in reverse order or not.
540 * <p>
541 * Not that this value is not affected by RecyclerView's layout direction.
543 * @return True if layout is reversed, false otherwise
544 * @see #setReverseLayout(boolean)
546 public boolean getReverseLayout() {
547 return mReverseLayout;
549 @Override
550 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
551 ensureOrientationHelper();
552 final AnchorInfo anchorInfo = mAnchorInfo;
553 anchorInfo.reset();
555 if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
556 if (state.getItemCount() == 0) {
557 removeAndRecycleAllViews(recycler);
558 return;
562 if (mPendingSavedState != null) {
563 applyPendingSavedState(anchorInfo);
564 } else {
565 resolveShouldLayoutReverse();
566 anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
569 updateAnchorInfoForLayout(state, anchorInfo);
571 if (mPendingSavedState == null) {
572 if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
573 isLayoutRTL() != mLastLayoutRTL) {
574 mLazySpanLookup.clear();
575 anchorInfo.mInvalidateOffsets = true;
579 if (getChildCount() > 0 && (mPendingSavedState == null ||
580 mPendingSavedState.mSpanOffsetsSize < 1)) {
581 if (anchorInfo.mInvalidateOffsets) {
582 for (int i = 0; i < mSpanCount; i++) {
583 // Scroll to position is set, clear.
584 mSpans[i].clear();
585 if (anchorInfo.mOffset != INVALID_OFFSET) {
586 mSpans[i].setLine(anchorInfo.mOffset);
589 } else {
590 for (int i = 0; i < mSpanCount; i++) {
591 mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset);
595 detachAndScrapAttachedViews(recycler);
596 mLaidOutInvalidFullSpan = false;
597 updateMeasureSpecs();
598 updateLayoutState(anchorInfo.mPosition, state);
599 if (anchorInfo.mLayoutFromEnd) {
600 // Layout start.
601 setLayoutStateDirection(LAYOUT_START);
602 fill(recycler, mLayoutState, state);
603 // Layout end.
604 setLayoutStateDirection(LAYOUT_END);
605 mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
606 fill(recycler, mLayoutState, state);
607 } else {
608 // Layout end.
609 setLayoutStateDirection(LAYOUT_END);
610 fill(recycler, mLayoutState, state);
611 // Layout start.
612 setLayoutStateDirection(LAYOUT_START);
613 mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
614 fill(recycler, mLayoutState, state);
617 if (getChildCount() > 0) {
618 if (mShouldReverseLayout) {
619 fixEndGap(recycler, state, true);
620 fixStartGap(recycler, state, false);
621 } else {
622 fixStartGap(recycler, state, true);
623 fixEndGap(recycler, state, false);
627 if (!state.isPreLayout()) {
628 final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
629 && getChildCount() > 0
630 && (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
631 if (needToCheckForGaps) {
632 removeCallbacks(mCheckForGapsRunnable);
633 postOnAnimation(mCheckForGapsRunnable);
635 mPendingScrollPosition = NO_POSITION;
636 mPendingScrollPositionOffset = INVALID_OFFSET;
638 mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
639 mLastLayoutRTL = isLayoutRTL();
640 mPendingSavedState = null; // we don't need this anymore
643 private void applyPendingSavedState(AnchorInfo anchorInfo) {
644 if (DEBUG) {
645 Log.d(TAG, "found saved state: " + mPendingSavedState);
647 if (mPendingSavedState.mSpanOffsetsSize > 0) {
648 if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
649 for (int i = 0; i < mSpanCount; i++) {
650 mSpans[i].clear();
651 int line = mPendingSavedState.mSpanOffsets[i];
652 if (line != Span.INVALID_LINE) {
653 if (mPendingSavedState.mAnchorLayoutFromEnd) {
654 line += mPrimaryOrientation.getEndAfterPadding();
655 } else {
656 line += mPrimaryOrientation.getStartAfterPadding();
659 mSpans[i].setLine(line);
661 } else {
662 mPendingSavedState.invalidateSpanInfo();
663 mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
666 mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
667 setReverseLayout(mPendingSavedState.mReverseLayout);
668 resolveShouldLayoutReverse();
670 if (mPendingSavedState.mAnchorPosition != NO_POSITION) {
671 mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
672 anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
673 } else {
674 anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
676 if (mPendingSavedState.mSpanLookupSize > 1) {
677 mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
678 mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
682 void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
683 if (updateAnchorFromPendingData(state, anchorInfo)) {
684 return;
686 if (updateAnchorFromChildren(state, anchorInfo)) {
687 return;
689 if (DEBUG) {
690 Log.d(TAG, "Deciding anchor info from fresh state");
692 anchorInfo.assignCoordinateFromPadding();
693 anchorInfo.mPosition = 0;
696 private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
697 // We don't recycle views out of adapter order. This way, we can rely on the first or
698 // last child as the anchor position.
699 // Layout direction may change but we should select the child depending on the latest
700 // layout direction. Otherwise, we'll choose the wrong child.
701 anchorInfo.mPosition = mLastLayoutFromEnd
702 ? findLastReferenceChildPosition(state.getItemCount())
703 : findFirstReferenceChildPosition(state.getItemCount());
704 anchorInfo.mOffset = INVALID_OFFSET;
705 return true;
708 boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
709 // Validate scroll position if exists.
710 if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
711 return false;
713 // Validate it.
714 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
715 mPendingScrollPosition = NO_POSITION;
716 mPendingScrollPositionOffset = INVALID_OFFSET;
717 return false;
720 if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION
721 || mPendingSavedState.mSpanOffsetsSize < 1) {
722 // If item is visible, make it fully visible.
723 final View child = findViewByPosition(mPendingScrollPosition);
724 if (child != null) {
725 // Use regular anchor position, offset according to pending offset and target
726 // child
727 anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
728 : getFirstChildPosition();
730 if (mPendingScrollPositionOffset != INVALID_OFFSET) {
731 if (anchorInfo.mLayoutFromEnd) {
732 final int target = mPrimaryOrientation.getEndAfterPadding() -
733 mPendingScrollPositionOffset;
734 anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
735 } else {
736 final int target = mPrimaryOrientation.getStartAfterPadding() +
737 mPendingScrollPositionOffset;
738 anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
740 return true;
743 // no offset provided. Decide according to the child location
744 final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
745 if (childSize > mPrimaryOrientation.getTotalSpace()) {
746 // Item does not fit. Fix depending on layout direction.
747 anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
748 ? mPrimaryOrientation.getEndAfterPadding()
749 : mPrimaryOrientation.getStartAfterPadding();
750 return true;
753 final int startGap = mPrimaryOrientation.getDecoratedStart(child)
754 - mPrimaryOrientation.getStartAfterPadding();
755 if (startGap < 0) {
756 anchorInfo.mOffset = -startGap;
757 return true;
759 final int endGap = mPrimaryOrientation.getEndAfterPadding() -
760 mPrimaryOrientation.getDecoratedEnd(child);
761 if (endGap < 0) {
762 anchorInfo.mOffset = endGap;
763 return true;
765 // child already visible. just layout as usual
766 anchorInfo.mOffset = INVALID_OFFSET;
767 } else {
768 // Child is not visible. Set anchor coordinate depending on in which direction
769 // child will be visible.
770 anchorInfo.mPosition = mPendingScrollPosition;
771 if (mPendingScrollPositionOffset == INVALID_OFFSET) {
772 final int position = calculateScrollDirectionForPosition(
773 anchorInfo.mPosition);
774 anchorInfo.mLayoutFromEnd = position == LAYOUT_END;
775 anchorInfo.assignCoordinateFromPadding();
776 } else {
777 anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
779 anchorInfo.mInvalidateOffsets = true;
781 } else {
782 anchorInfo.mOffset = INVALID_OFFSET;
783 anchorInfo.mPosition = mPendingScrollPosition;
785 return true;
788 void updateMeasureSpecs() {
789 mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
790 mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
791 mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
792 if (mOrientation == VERTICAL) {
793 mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
794 mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
795 } else {
796 mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
797 mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
801 @Override
802 public boolean supportsPredictiveItemAnimations() {
803 return mPendingSavedState == null;
807 * Returns the adapter position of the first visible view for each span.
808 * <p>
809 * Note that, this value is not affected by layout orientation or item order traversal.
810 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
811 * not in the layout.
812 * <p>
813 * If RecyclerView has item decorators, they will be considered in calculations as well.
814 * <p>
815 * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
816 * views are ignored in this method.
818 * @param into An array to put the results into. If you don't provide any, LayoutManager will
819 * create a new one.
820 * @return The adapter position of the first visible item in each span. If a span does not have
821 * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
822 * @see #findFirstCompletelyVisibleItemPositions(int[])
823 * @see #findLastVisibleItemPositions(int[])
825 public int[] findFirstVisibleItemPositions(int[] into) {
826 if (into == null) {
827 into = new int[mSpanCount];
828 } else if (into.length < mSpanCount) {
829 throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
830 + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
832 for (int i = 0; i < mSpanCount; i++) {
833 into[i] = mSpans[i].findFirstVisibleItemPosition();
835 return into;
839 * Returns the adapter position of the first completely visible view for each span.
840 * <p>
841 * Note that, this value is not affected by layout orientation or item order traversal.
842 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
843 * not in the layout.
844 * <p>
845 * If RecyclerView has item decorators, they will be considered in calculations as well.
846 * <p>
847 * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
848 * views are ignored in this method.
850 * @param into An array to put the results into. If you don't provide any, LayoutManager will
851 * create a new one.
852 * @return The adapter position of the first fully visible item in each span. If a span does
853 * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
854 * @see #findFirstVisibleItemPositions(int[])
855 * @see #findLastCompletelyVisibleItemPositions(int[])
857 public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
858 if (into == null) {
859 into = new int[mSpanCount];
860 } else if (into.length < mSpanCount) {
861 throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
862 + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
864 for (int i = 0; i < mSpanCount; i++) {
865 into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
867 return into;
871 * Returns the adapter position of the last visible view for each span.
872 * <p>
873 * Note that, this value is not affected by layout orientation or item order traversal.
874 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
875 * not in the layout.
876 * <p>
877 * If RecyclerView has item decorators, they will be considered in calculations as well.
878 * <p>
879 * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
880 * views are ignored in this method.
882 * @param into An array to put the results into. If you don't provide any, LayoutManager will
883 * create a new one.
884 * @return The adapter position of the last visible item in each span. If a span does not have
885 * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
886 * @see #findLastCompletelyVisibleItemPositions(int[])
887 * @see #findFirstVisibleItemPositions(int[])
889 public int[] findLastVisibleItemPositions(int[] into) {
890 if (into == null) {
891 into = new int[mSpanCount];
892 } else if (into.length < mSpanCount) {
893 throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
894 + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
896 for (int i = 0; i < mSpanCount; i++) {
897 into[i] = mSpans[i].findLastVisibleItemPosition();
899 return into;
903 * Returns the adapter position of the last completely visible view for each span.
904 * <p>
905 * Note that, this value is not affected by layout orientation or item order traversal.
906 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
907 * not in the layout.
908 * <p>
909 * If RecyclerView has item decorators, they will be considered in calculations as well.
910 * <p>
911 * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
912 * views are ignored in this method.
914 * @param into An array to put the results into. If you don't provide any, LayoutManager will
915 * create a new one.
916 * @return The adapter position of the last fully visible item in each span. If a span does not
917 * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
918 * @see #findFirstCompletelyVisibleItemPositions(int[])
919 * @see #findLastVisibleItemPositions(int[])
921 public int[] findLastCompletelyVisibleItemPositions(int[] into) {
922 if (into == null) {
923 into = new int[mSpanCount];
924 } else if (into.length < mSpanCount) {
925 throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
926 + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
928 for (int i = 0; i < mSpanCount; i++) {
929 into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
931 return into;
934 @Override
935 public int computeHorizontalScrollOffset(RecyclerView.State state) {
936 return computeScrollOffset(state);
939 private int computeScrollOffset(RecyclerView.State state) {
940 if (getChildCount() == 0) {
941 return 0;
943 ensureOrientationHelper();
944 return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
945 findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
946 , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
947 this, mSmoothScrollbarEnabled, mShouldReverseLayout);
950 @Override
951 public int computeVerticalScrollOffset(RecyclerView.State state) {
952 return computeScrollOffset(state);
955 @Override
956 public int computeHorizontalScrollExtent(RecyclerView.State state) {
957 return computeScrollExtent(state);
960 private int computeScrollExtent(RecyclerView.State state) {
961 if (getChildCount() == 0) {
962 return 0;
964 ensureOrientationHelper();
965 return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
966 findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
967 , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
968 this, mSmoothScrollbarEnabled);
971 @Override
972 public int computeVerticalScrollExtent(RecyclerView.State state) {
973 return computeScrollExtent(state);
976 @Override
977 public int computeHorizontalScrollRange(RecyclerView.State state) {
978 return computeScrollRange(state);
981 private int computeScrollRange(RecyclerView.State state) {
982 if (getChildCount() == 0) {
983 return 0;
985 ensureOrientationHelper();
986 return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
987 findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
988 , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
989 this, mSmoothScrollbarEnabled);
992 @Override
993 public int computeVerticalScrollRange(RecyclerView.State state) {
994 return computeScrollRange(state);
997 private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
998 if (lp.mFullSpan) {
999 if (mOrientation == VERTICAL) {
1000 measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
1001 getSpecForDimension(lp.height, mHeightSpec));
1002 } else {
1003 measureChildWithDecorationsAndMargin(child,
1004 getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
1006 } else {
1007 if (mOrientation == VERTICAL) {
1008 measureChildWithDecorationsAndMargin(child, mWidthSpec,
1009 getSpecForDimension(lp.height, mHeightSpec));
1010 } else {
1011 measureChildWithDecorationsAndMargin(child,
1012 getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
1017 private int getSpecForDimension(int dim, int defaultSpec) {
1018 if (dim < 0) {
1019 return defaultSpec;
1020 } else {
1021 return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
1025 private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
1026 int heightSpec) {
1027 calculateItemDecorationsForChild(child, mTmpRect);
1028 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1029 widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
1030 lp.rightMargin + mTmpRect.right);
1031 heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
1032 lp.bottomMargin + mTmpRect.bottom);
1033 child.measure(widthSpec, heightSpec);
1036 private int updateSpecWithExtra(int spec, int startInset, int endInset) {
1037 if (startInset == 0 && endInset == 0) {
1038 return spec;
1040 final int mode = View.MeasureSpec.getMode(spec);
1041 if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
1042 return View.MeasureSpec.makeMeasureSpec(
1043 View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
1045 return spec;
1048 @Override
1049 public void onRestoreInstanceState(Parcelable state) {
1050 if (state instanceof SavedState) {
1051 mPendingSavedState = (SavedState) state;
1052 requestLayout();
1053 } else if (DEBUG) {
1054 Log.d(TAG, "invalid saved state class");
1058 @Override
1059 public Parcelable onSaveInstanceState() {
1060 if (mPendingSavedState != null) {
1061 return new SavedState(mPendingSavedState);
1063 SavedState state = new SavedState();
1064 state.mReverseLayout = mReverseLayout;
1065 state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
1066 state.mLastLayoutRTL = mLastLayoutRTL;
1068 if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
1069 state.mSpanLookup = mLazySpanLookup.mData;
1070 state.mSpanLookupSize = state.mSpanLookup.length;
1071 state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
1072 } else {
1073 state.mSpanLookupSize = 0;
1076 if (getChildCount() > 0) {
1077 ensureOrientationHelper();
1078 state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
1079 : getFirstChildPosition();
1080 state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
1081 state.mSpanOffsetsSize = mSpanCount;
1082 state.mSpanOffsets = new int[mSpanCount];
1083 for (int i = 0; i < mSpanCount; i++) {
1084 int line;
1085 if (mLastLayoutFromEnd) {
1086 line = mSpans[i].getEndLine(Span.INVALID_LINE);
1087 if (line != Span.INVALID_LINE) {
1088 line -= mPrimaryOrientation.getEndAfterPadding();
1090 } else {
1091 line = mSpans[i].getStartLine(Span.INVALID_LINE);
1092 if (line != Span.INVALID_LINE) {
1093 line -= mPrimaryOrientation.getStartAfterPadding();
1096 state.mSpanOffsets[i] = line;
1098 } else {
1099 state.mAnchorPosition = NO_POSITION;
1100 state.mVisibleAnchorPosition = NO_POSITION;
1101 state.mSpanOffsetsSize = 0;
1103 if (DEBUG) {
1104 Log.d(TAG, "saved state:\n" + state);
1106 return state;
1109 @Override
1110 public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
1111 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
1112 ViewGroup.LayoutParams lp = host.getLayoutParams();
1113 if (!(lp instanceof LayoutParams)) {
1114 super.onInitializeAccessibilityNodeInfoForItem(host, info);
1115 return;
1117 LayoutParams sglp = (LayoutParams) lp;
1118 if (mOrientation == HORIZONTAL) {
1119 info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1120 sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1121 -1, -1,
1122 sglp.mFullSpan, false));
1123 } else { // VERTICAL
1124 info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
1125 -1, -1,
1126 sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1,
1127 sglp.mFullSpan, false));
1131 @Override
1132 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1133 super.onInitializeAccessibilityEvent(event);
1134 if (getChildCount() > 0) {
1135 final AccessibilityRecordCompat record = AccessibilityEventCompat
1136 .asRecord(event);
1137 final View start = findFirstVisibleItemClosestToStart(false, true);
1138 final View end = findFirstVisibleItemClosestToEnd(false, true);
1139 if (start == null || end == null) {
1140 return;
1142 final int startPos = getPosition(start);
1143 final int endPos = getPosition(end);
1144 if (startPos < endPos) {
1145 record.setFromIndex(startPos);
1146 record.setToIndex(endPos);
1147 } else {
1148 record.setFromIndex(endPos);
1149 record.setToIndex(startPos);
1155 * Finds the first fully visible child to be used as an anchor child if span count changes when
1156 * state is restored. If no children is fully visible, returns a partially visible child instead
1157 * of returning null.
1159 int findFirstVisibleItemPositionInt() {
1160 final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) :
1161 findFirstVisibleItemClosestToStart(true, true);
1162 return first == null ? NO_POSITION : getPosition(first);
1165 @Override
1166 public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
1167 RecyclerView.State state) {
1168 if (mOrientation == HORIZONTAL) {
1169 return mSpanCount;
1171 return super.getRowCountForAccessibility(recycler, state);
1174 @Override
1175 public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
1176 RecyclerView.State state) {
1177 if (mOrientation == VERTICAL) {
1178 return mSpanCount;
1180 return super.getColumnCountForAccessibility(recycler, state);
1184 * This is for internal use. Not necessarily the child closest to start but the first child
1185 * we find that matches the criteria.
1186 * This method does not do any sorting based on child's start coordinate, instead, it uses
1187 * children order.
1189 View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) {
1190 ensureOrientationHelper();
1191 final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1192 final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1193 final int limit = getChildCount();
1194 View partiallyVisible = null;
1195 for (int i = 0; i < limit; i++) {
1196 final View child = getChildAt(i);
1197 final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1198 final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1199 if(childEnd <= boundsStart || childStart >= boundsEnd) {
1200 continue; // not visible at all
1202 if (childStart >= boundsStart || !fullyVisible) {
1203 // when checking for start, it is enough even if part of the child's top is visible
1204 // as long as fully visible is not requested.
1205 return child;
1207 if (acceptPartiallyVisible && partiallyVisible == null) {
1208 partiallyVisible = child;
1211 return partiallyVisible;
1215 * This is for internal use. Not necessarily the child closest to bottom but the first child
1216 * we find that matches the criteria.
1217 * This method does not do any sorting based on child's end coordinate, instead, it uses
1218 * children order.
1220 View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) {
1221 ensureOrientationHelper();
1222 final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
1223 final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
1224 View partiallyVisible = null;
1225 for (int i = getChildCount() - 1; i >= 0; i--) {
1226 final View child = getChildAt(i);
1227 final int childStart = mPrimaryOrientation.getDecoratedStart(child);
1228 final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
1229 if(childEnd <= boundsStart || childStart >= boundsEnd) {
1230 continue; // not visible at all
1232 if (childEnd <= boundsEnd || !fullyVisible) {
1233 // when checking for end, it is enough even if part of the child's bottom is visible
1234 // as long as fully visible is not requested.
1235 return child;
1237 if (acceptPartiallyVisible && partiallyVisible == null) {
1238 partiallyVisible = child;
1241 return partiallyVisible;
1244 private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1245 boolean canOffsetChildren) {
1246 final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1247 int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
1248 int fixOffset;
1249 if (gap > 0) {
1250 fixOffset = -scrollBy(-gap, recycler, state);
1251 } else {
1252 return; // nothing to fix
1254 gap -= fixOffset;
1255 if (canOffsetChildren && gap > 0) {
1256 mPrimaryOrientation.offsetChildren(gap);
1260 private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
1261 boolean canOffsetChildren) {
1262 final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1263 int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
1264 int fixOffset;
1265 if (gap > 0) {
1266 fixOffset = scrollBy(gap, recycler, state);
1267 } else {
1268 return; // nothing to fix
1270 gap -= fixOffset;
1271 if (canOffsetChildren && gap > 0) {
1272 mPrimaryOrientation.offsetChildren(-gap);
1276 private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
1277 mLayoutState.mAvailable = 0;
1278 mLayoutState.mCurrentPosition = anchorPosition;
1279 int startExtra = 0;
1280 int endExtra = 0;
1281 if (isSmoothScrolling()) {
1282 final int targetPos = state.getTargetScrollPosition();
1283 if (targetPos != NO_POSITION) {
1284 if (mShouldReverseLayout == targetPos < anchorPosition) {
1285 endExtra = mPrimaryOrientation.getTotalSpace();
1286 } else {
1287 startExtra = mPrimaryOrientation.getTotalSpace();
1292 // Line of the furthest row.
1293 final boolean clipToPadding = getClipToPadding();
1294 if (clipToPadding) {
1295 mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
1296 mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
1297 } else {
1298 mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
1299 mLayoutState.mStartLine = -startExtra;
1303 private void setLayoutStateDirection(int direction) {
1304 mLayoutState.mLayoutDirection = direction;
1305 mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LAYOUT_START)) ?
1306 ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
1309 @Override
1310 public void offsetChildrenHorizontal(int dx) {
1311 super.offsetChildrenHorizontal(dx);
1312 for (int i = 0; i < mSpanCount; i++) {
1313 mSpans[i].onOffset(dx);
1317 @Override
1318 public void offsetChildrenVertical(int dy) {
1319 super.offsetChildrenVertical(dy);
1320 for (int i = 0; i < mSpanCount; i++) {
1321 mSpans[i].onOffset(dy);
1325 @Override
1326 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
1327 handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
1330 @Override
1331 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
1332 handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
1335 @Override
1336 public void onItemsChanged(RecyclerView recyclerView) {
1337 mLazySpanLookup.clear();
1338 requestLayout();
1341 @Override
1342 public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
1343 handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
1346 @Override
1347 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
1348 Object payload) {
1349 handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
1353 * Checks whether it should invalidate span assignments in response to an adapter change.
1355 private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
1356 int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
1357 final int affectedRangeEnd;// exclusive
1358 final int affectedRangeStart;// inclusive
1360 if (cmd == AdapterHelper.UpdateOp.MOVE) {
1361 if (positionStart < itemCountOrToPosition) {
1362 affectedRangeEnd = itemCountOrToPosition + 1;
1363 affectedRangeStart = positionStart;
1364 } else {
1365 affectedRangeEnd = positionStart + 1;
1366 affectedRangeStart = itemCountOrToPosition;
1368 } else {
1369 affectedRangeStart = positionStart;
1370 affectedRangeEnd = positionStart + itemCountOrToPosition;
1373 mLazySpanLookup.invalidateAfter(affectedRangeStart);
1374 switch (cmd) {
1375 case AdapterHelper.UpdateOp.ADD:
1376 mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
1377 break;
1378 case AdapterHelper.UpdateOp.REMOVE:
1379 mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
1380 break;
1381 case AdapterHelper.UpdateOp.MOVE:
1382 // TODO optimize
1383 mLazySpanLookup.offsetForRemoval(positionStart, 1);
1384 mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
1385 break;
1388 if (affectedRangeEnd <= minPosition) {
1389 return;
1392 int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
1393 if (affectedRangeStart <= maxPosition) {
1394 requestLayout();
1398 private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
1399 RecyclerView.State state) {
1400 mRemainingSpans.set(0, mSpanCount, true);
1401 // The target position we are trying to reach.
1402 final int targetLine;
1404 // Line of the furthest row.
1405 if (layoutState.mLayoutDirection == LAYOUT_END) {
1406 targetLine = layoutState.mEndLine + layoutState.mAvailable;
1407 } else { // LAYOUT_START
1408 targetLine = layoutState.mStartLine - layoutState.mAvailable;
1411 updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
1412 if (DEBUG) {
1413 Log.d(TAG, "FILLING targetLine: " + targetLine + "," +
1414 "remaining spans:" + mRemainingSpans + ", state: " + layoutState);
1417 // the default coordinate to add new view.
1418 final int defaultNewViewLine = mShouldReverseLayout
1419 ? mPrimaryOrientation.getEndAfterPadding()
1420 : mPrimaryOrientation.getStartAfterPadding();
1421 boolean added = false;
1422 while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
1423 View view = layoutState.next(recycler);
1424 LayoutParams lp = ((LayoutParams) view.getLayoutParams());
1425 final int position = lp.getViewLayoutPosition();
1426 final int spanIndex = mLazySpanLookup.getSpan(position);
1427 Span currentSpan;
1428 final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
1429 if (assignSpan) {
1430 currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
1431 mLazySpanLookup.setSpan(position, currentSpan);
1432 if (DEBUG) {
1433 Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
1435 } else {
1436 if (DEBUG) {
1437 Log.d(TAG, "using " + spanIndex + " for pos " + position);
1439 currentSpan = mSpans[spanIndex];
1441 // assign span before measuring so that item decorators can get updated span index
1442 lp.mSpan = currentSpan;
1443 if (layoutState.mLayoutDirection == LAYOUT_END) {
1444 addView(view);
1445 } else {
1446 addView(view, 0);
1448 measureChildWithDecorationsAndMargin(view, lp);
1450 final int start;
1451 final int end;
1452 if (layoutState.mLayoutDirection == LAYOUT_END) {
1453 start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
1454 : currentSpan.getEndLine(defaultNewViewLine);
1455 end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
1456 if (assignSpan && lp.mFullSpan) {
1457 LazySpanLookup.FullSpanItem fullSpanItem;
1458 fullSpanItem = createFullSpanItemFromEnd(start);
1459 fullSpanItem.mGapDir = LAYOUT_START;
1460 fullSpanItem.mPosition = position;
1461 mLazySpanLookup.addFullSpanItem(fullSpanItem);
1463 } else {
1464 end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
1465 : currentSpan.getStartLine(defaultNewViewLine);
1466 start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
1467 if (assignSpan && lp.mFullSpan) {
1468 LazySpanLookup.FullSpanItem fullSpanItem;
1469 fullSpanItem = createFullSpanItemFromStart(end);
1470 fullSpanItem.mGapDir = LAYOUT_END;
1471 fullSpanItem.mPosition = position;
1472 mLazySpanLookup.addFullSpanItem(fullSpanItem);
1476 // check if this item may create gaps in the future
1477 if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
1478 if (assignSpan) {
1479 mLaidOutInvalidFullSpan = true;
1480 } else {
1481 final boolean hasInvalidGap;
1482 if (layoutState.mLayoutDirection == LAYOUT_END) {
1483 hasInvalidGap = !areAllEndsEqual();
1484 } else { // layoutState.mLayoutDirection == LAYOUT_START
1485 hasInvalidGap = !areAllStartsEqual();
1487 if (hasInvalidGap) {
1488 final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
1489 .getFullSpanItem(position);
1490 if (fullSpanItem != null) {
1491 fullSpanItem.mHasUnwantedGapAfter = true;
1493 mLaidOutInvalidFullSpan = true;
1498 attachViewToSpans(view, lp, layoutState);
1499 final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
1500 : currentSpan.mIndex * mSizePerSpan +
1501 mSecondaryOrientation.getStartAfterPadding();
1502 final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
1503 if (mOrientation == VERTICAL) {
1504 layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
1505 } else {
1506 layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
1509 if (lp.mFullSpan) {
1510 updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
1511 } else {
1512 updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
1514 recycle(recycler, mLayoutState);
1515 added = true;
1517 if (!added) {
1518 recycle(recycler, mLayoutState);
1520 final int diff;
1521 if (mLayoutState.mLayoutDirection == LAYOUT_START) {
1522 final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
1523 diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
1524 } else {
1525 final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
1526 diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
1528 return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
1531 private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
1532 LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1533 fsi.mGapPerSpan = new int[mSpanCount];
1534 for (int i = 0; i < mSpanCount; i++) {
1535 fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
1537 return fsi;
1540 private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
1541 LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
1542 fsi.mGapPerSpan = new int[mSpanCount];
1543 for (int i = 0; i < mSpanCount; i++) {
1544 fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
1546 return fsi;
1549 private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) {
1550 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
1551 if (lp.mFullSpan) {
1552 appendViewToAllSpans(view);
1553 } else {
1554 lp.mSpan.appendToSpan(view);
1556 } else {
1557 if (lp.mFullSpan) {
1558 prependViewToAllSpans(view);
1559 } else {
1560 lp.mSpan.prependToSpan(view);
1565 private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
1566 if (layoutState.mAvailable == 0) {
1567 // easy, recycle line is still valid
1568 if (layoutState.mLayoutDirection == LAYOUT_START) {
1569 recycleFromEnd(recycler, layoutState.mEndLine);
1570 } else {
1571 recycleFromStart(recycler, layoutState.mStartLine);
1573 } else {
1574 // scrolling case, recycle line can be shifted by how much space we could cover
1575 // by adding new views
1576 if (layoutState.mLayoutDirection == LAYOUT_START) {
1577 // calculate recycle line
1578 int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
1579 final int line;
1580 if (scrolled < 0) {
1581 line = layoutState.mEndLine;
1582 } else {
1583 line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
1585 recycleFromEnd(recycler, line);
1586 } else {
1587 // calculate recycle line
1588 int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
1589 final int line;
1590 if (scrolled < 0) {
1591 line = layoutState.mStartLine;
1592 } else {
1593 line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
1595 recycleFromStart(recycler, line);
1601 private void appendViewToAllSpans(View view) {
1602 // traverse in reverse so that we end up assigning full span items to 0
1603 for (int i = mSpanCount - 1; i >= 0; i--) {
1604 mSpans[i].appendToSpan(view);
1608 private void prependViewToAllSpans(View view) {
1609 // traverse in reverse so that we end up assigning full span items to 0
1610 for (int i = mSpanCount - 1; i >= 0; i--) {
1611 mSpans[i].prependToSpan(view);
1615 private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
1616 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1617 if (DEBUG) {
1618 Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:"
1619 + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
1620 + ". l:" + left + ",t:" + top
1621 + ", r:" + right + ", b:" + bottom);
1623 layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
1624 , bottom - lp.bottomMargin);
1627 private void updateAllRemainingSpans(int layoutDir, int targetLine) {
1628 for (int i = 0; i < mSpanCount; i++) {
1629 if (mSpans[i].mViews.isEmpty()) {
1630 continue;
1632 updateRemainingSpans(mSpans[i], layoutDir, targetLine);
1636 private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
1637 final int deletedSize = span.getDeletedSize();
1638 if (layoutDir == LAYOUT_START) {
1639 final int line = span.getStartLine();
1640 if (line + deletedSize <= targetLine) {
1641 mRemainingSpans.set(span.mIndex, false);
1643 } else {
1644 final int line = span.getEndLine();
1645 if (line - deletedSize >= targetLine) {
1646 mRemainingSpans.set(span.mIndex, false);
1651 private int getMaxStart(int def) {
1652 int maxStart = mSpans[0].getStartLine(def);
1653 for (int i = 1; i < mSpanCount; i++) {
1654 final int spanStart = mSpans[i].getStartLine(def);
1655 if (spanStart > maxStart) {
1656 maxStart = spanStart;
1659 return maxStart;
1662 private int getMinStart(int def) {
1663 int minStart = mSpans[0].getStartLine(def);
1664 for (int i = 1; i < mSpanCount; i++) {
1665 final int spanStart = mSpans[i].getStartLine(def);
1666 if (spanStart < minStart) {
1667 minStart = spanStart;
1670 return minStart;
1673 boolean areAllEndsEqual() {
1674 int end = mSpans[0].getEndLine(Span.INVALID_LINE);
1675 for (int i = 1; i < mSpanCount; i++) {
1676 if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
1677 return false;
1680 return true;
1683 boolean areAllStartsEqual() {
1684 int start = mSpans[0].getStartLine(Span.INVALID_LINE);
1685 for (int i = 1; i < mSpanCount; i++) {
1686 if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
1687 return false;
1690 return true;
1693 private int getMaxEnd(int def) {
1694 int maxEnd = mSpans[0].getEndLine(def);
1695 for (int i = 1; i < mSpanCount; i++) {
1696 final int spanEnd = mSpans[i].getEndLine(def);
1697 if (spanEnd > maxEnd) {
1698 maxEnd = spanEnd;
1701 return maxEnd;
1704 private int getMinEnd(int def) {
1705 int minEnd = mSpans[0].getEndLine(def);
1706 for (int i = 1; i < mSpanCount; i++) {
1707 final int spanEnd = mSpans[i].getEndLine(def);
1708 if (spanEnd < minEnd) {
1709 minEnd = spanEnd;
1712 return minEnd;
1715 private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
1716 while (getChildCount() > 0) {
1717 View child = getChildAt(0);
1718 if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
1719 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1720 // Don't recycle the last View in a span not to lose span's start/end lines
1721 if (lp.mFullSpan) {
1722 for (int j = 0; j < mSpanCount; j++) {
1723 if (mSpans[j].mViews.size() == 1) {
1724 return;
1727 for (int j = 0; j < mSpanCount; j++) {
1728 mSpans[j].popStart();
1730 } else {
1731 if (lp.mSpan.mViews.size() == 1) {
1732 return;
1734 lp.mSpan.popStart();
1736 removeAndRecycleView(child, recycler);
1737 } else {
1738 return;// done
1743 private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
1744 final int childCount = getChildCount();
1745 int i;
1746 for (i = childCount - 1; i >= 0; i--) {
1747 View child = getChildAt(i);
1748 if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
1749 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1750 // Don't recycle the last View in a span not to lose span's start/end lines
1751 if (lp.mFullSpan) {
1752 for (int j = 0; j < mSpanCount; j++) {
1753 if (mSpans[j].mViews.size() == 1) {
1754 return;
1757 for (int j = 0; j < mSpanCount; j++) {
1758 mSpans[j].popEnd();
1760 } else {
1761 if (lp.mSpan.mViews.size() == 1) {
1762 return;
1764 lp.mSpan.popEnd();
1766 removeAndRecycleView(child, recycler);
1767 } else {
1768 return;// done
1774 * @return True if last span is the first one we want to fill
1776 private boolean preferLastSpan(int layoutDir) {
1777 if (mOrientation == HORIZONTAL) {
1778 return (layoutDir == LAYOUT_START) != mShouldReverseLayout;
1780 return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
1784 * Finds the span for the next view.
1786 private Span getNextSpan(LayoutState layoutState) {
1787 final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
1788 final int startIndex, endIndex, diff;
1789 if (preferLastSpan) {
1790 startIndex = mSpanCount - 1;
1791 endIndex = -1;
1792 diff = -1;
1793 } else {
1794 startIndex = 0;
1795 endIndex = mSpanCount;
1796 diff = 1;
1798 if (layoutState.mLayoutDirection == LAYOUT_END) {
1799 Span min = null;
1800 int minLine = Integer.MAX_VALUE;
1801 final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
1802 for (int i = startIndex; i != endIndex; i += diff) {
1803 final Span other = mSpans[i];
1804 int otherLine = other.getEndLine(defaultLine);
1805 if (otherLine < minLine) {
1806 min = other;
1807 minLine = otherLine;
1810 return min;
1811 } else {
1812 Span max = null;
1813 int maxLine = Integer.MIN_VALUE;
1814 final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
1815 for (int i = startIndex; i != endIndex; i += diff) {
1816 final Span other = mSpans[i];
1817 int otherLine = other.getStartLine(defaultLine);
1818 if (otherLine > maxLine) {
1819 max = other;
1820 maxLine = otherLine;
1823 return max;
1827 @Override
1828 public boolean canScrollVertically() {
1829 return mOrientation == VERTICAL;
1832 @Override
1833 public boolean canScrollHorizontally() {
1834 return mOrientation == HORIZONTAL;
1837 @Override
1838 public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
1839 RecyclerView.State state) {
1840 return scrollBy(dx, recycler, state);
1843 @Override
1844 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
1845 RecyclerView.State state) {
1846 return scrollBy(dy, recycler, state);
1849 private int calculateScrollDirectionForPosition(int position) {
1850 if (getChildCount() == 0) {
1851 return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
1853 final int firstChildPos = getFirstChildPosition();
1854 return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
1857 @Override
1858 public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
1859 int position) {
1860 LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
1861 @Override
1862 public PointF computeScrollVectorForPosition(int targetPosition) {
1863 final int direction = calculateScrollDirectionForPosition(targetPosition);
1864 if (direction == 0) {
1865 return null;
1867 if (mOrientation == HORIZONTAL) {
1868 return new PointF(direction, 0);
1869 } else {
1870 return new PointF(0, direction);
1874 scroller.setTargetPosition(position);
1875 startSmoothScroll(scroller);
1878 @Override
1879 public void scrollToPosition(int position) {
1880 if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
1881 mPendingSavedState.invalidateAnchorPositionInfo();
1883 mPendingScrollPosition = position;
1884 mPendingScrollPositionOffset = INVALID_OFFSET;
1885 requestLayout();
1889 * Scroll to the specified adapter position with the given offset from layout start.
1890 * <p>
1891 * Note that scroll position change will not be reflected until the next layout call.
1892 * <p>
1893 * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
1895 * @param position Index (starting at 0) of the reference item.
1896 * @param offset The distance (in pixels) between the start edge of the item view and
1897 * start edge of the RecyclerView.
1898 * @see #setReverseLayout(boolean)
1899 * @see #scrollToPosition(int)
1901 public void scrollToPositionWithOffset(int position, int offset) {
1902 if (mPendingSavedState != null) {
1903 mPendingSavedState.invalidateAnchorPositionInfo();
1905 mPendingScrollPosition = position;
1906 mPendingScrollPositionOffset = offset;
1907 requestLayout();
1910 int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
1911 ensureOrientationHelper();
1912 final int referenceChildPosition;
1913 final int layoutDir;
1914 if (dt > 0) { // layout towards end
1915 layoutDir = LAYOUT_END;
1916 referenceChildPosition = getLastChildPosition();
1917 } else {
1918 layoutDir = LAYOUT_START;
1919 referenceChildPosition = getFirstChildPosition();
1921 updateLayoutState(referenceChildPosition, state);
1922 setLayoutStateDirection(layoutDir);
1923 mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
1924 final int absDt = Math.abs(dt);
1925 mLayoutState.mAvailable = absDt;
1926 int consumed = fill(recycler, mLayoutState, state);
1927 final int totalScroll;
1928 if (absDt < consumed) {
1929 totalScroll = dt;
1930 } else if (dt < 0) {
1931 totalScroll = -consumed;
1932 } else { // dt > 0
1933 totalScroll = consumed;
1935 if (DEBUG) {
1936 Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
1939 mPrimaryOrientation.offsetChildren(-totalScroll);
1940 // always reset this if we scroll for a proper save instance state
1941 mLastLayoutFromEnd = mShouldReverseLayout;
1942 return totalScroll;
1945 private int getLastChildPosition() {
1946 final int childCount = getChildCount();
1947 return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
1950 private int getFirstChildPosition() {
1951 final int childCount = getChildCount();
1952 return childCount == 0 ? 0 : getPosition(getChildAt(0));
1956 * Finds the first View that can be used as an anchor View.
1958 * @return Position of the View or 0 if it cannot find any such View.
1960 private int findFirstReferenceChildPosition(int itemCount) {
1961 final int limit = getChildCount();
1962 for (int i = 0; i < limit; i++) {
1963 final View view = getChildAt(i);
1964 final int position = getPosition(view);
1965 if (position >= 0 && position < itemCount) {
1966 return position;
1969 return 0;
1973 * Finds the last View that can be used as an anchor View.
1975 * @return Position of the View or 0 if it cannot find any such View.
1977 private int findLastReferenceChildPosition(int itemCount) {
1978 for (int i = getChildCount() - 1; i >= 0; i--) {
1979 final View view = getChildAt(i);
1980 final int position = getPosition(view);
1981 if (position >= 0 && position < itemCount) {
1982 return position;
1985 return 0;
1988 @Override
1989 public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1990 return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1991 ViewGroup.LayoutParams.WRAP_CONTENT);
1994 @Override
1995 public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
1996 return new LayoutParams(c, attrs);
1999 @Override
2000 public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
2001 if (lp instanceof ViewGroup.MarginLayoutParams) {
2002 return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
2003 } else {
2004 return new LayoutParams(lp);
2008 @Override
2009 public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
2010 return lp instanceof LayoutParams;
2013 public int getOrientation() {
2014 return mOrientation;
2019 * LayoutParams used by StaggeredGridLayoutManager.
2020 * <p>
2021 * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
2022 * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
2023 * expected to fill all of the space given to it.
2025 public static class LayoutParams extends RecyclerView.LayoutParams {
2028 * Span Id for Views that are not laid out yet.
2030 public static final int INVALID_SPAN_ID = -1;
2032 // Package scope to be able to access from tests.
2033 Span mSpan;
2035 boolean mFullSpan;
2037 public LayoutParams(Context c, AttributeSet attrs) {
2038 super(c, attrs);
2041 public LayoutParams(int width, int height) {
2042 super(width, height);
2045 public LayoutParams(ViewGroup.MarginLayoutParams source) {
2046 super(source);
2049 public LayoutParams(ViewGroup.LayoutParams source) {
2050 super(source);
2053 public LayoutParams(RecyclerView.LayoutParams source) {
2054 super(source);
2058 * When set to true, the item will layout using all span area. That means, if orientation
2059 * is vertical, the view will have full width; if orientation is horizontal, the view will
2060 * have full height.
2062 * @param fullSpan True if this item should traverse all spans.
2063 * @see #isFullSpan()
2065 public void setFullSpan(boolean fullSpan) {
2066 mFullSpan = fullSpan;
2070 * Returns whether this View occupies all available spans or just one.
2072 * @return True if the View occupies all spans or false otherwise.
2073 * @see #setFullSpan(boolean)
2075 public boolean isFullSpan() {
2076 return mFullSpan;
2080 * Returns the Span index to which this View is assigned.
2082 * @return The Span index of the View. If View is not yet assigned to any span, returns
2083 * {@link #INVALID_SPAN_ID}.
2085 public final int getSpanIndex() {
2086 if (mSpan == null) {
2087 return INVALID_SPAN_ID;
2089 return mSpan.mIndex;
2093 // Package scoped to access from tests.
2094 class Span {
2096 static final int INVALID_LINE = Integer.MIN_VALUE;
2097 private ArrayList<View> mViews = new ArrayList<View>();
2098 int mCachedStart = INVALID_LINE;
2099 int mCachedEnd = INVALID_LINE;
2100 int mDeletedSize = 0;
2101 final int mIndex;
2103 private Span(int index) {
2104 mIndex = index;
2107 int getStartLine(int def) {
2108 if (mCachedStart != INVALID_LINE) {
2109 return mCachedStart;
2111 if (mViews.size() == 0) {
2112 return def;
2114 calculateCachedStart();
2115 return mCachedStart;
2118 void calculateCachedStart() {
2119 final View startView = mViews.get(0);
2120 final LayoutParams lp = getLayoutParams(startView);
2121 mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
2122 if (lp.mFullSpan) {
2123 LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2124 .getFullSpanItem(lp.getViewLayoutPosition());
2125 if (fsi != null && fsi.mGapDir == LAYOUT_START) {
2126 mCachedStart -= fsi.getGapForSpan(mIndex);
2131 // Use this one when default value does not make sense and not having a value means a bug.
2132 int getStartLine() {
2133 if (mCachedStart != INVALID_LINE) {
2134 return mCachedStart;
2136 calculateCachedStart();
2137 return mCachedStart;
2140 int getEndLine(int def) {
2141 if (mCachedEnd != INVALID_LINE) {
2142 return mCachedEnd;
2144 final int size = mViews.size();
2145 if (size == 0) {
2146 return def;
2148 calculateCachedEnd();
2149 return mCachedEnd;
2152 void calculateCachedEnd() {
2153 final View endView = mViews.get(mViews.size() - 1);
2154 final LayoutParams lp = getLayoutParams(endView);
2155 mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
2156 if (lp.mFullSpan) {
2157 LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
2158 .getFullSpanItem(lp.getViewLayoutPosition());
2159 if (fsi != null && fsi.mGapDir == LAYOUT_END) {
2160 mCachedEnd += fsi.getGapForSpan(mIndex);
2165 // Use this one when default value does not make sense and not having a value means a bug.
2166 int getEndLine() {
2167 if (mCachedEnd != INVALID_LINE) {
2168 return mCachedEnd;
2170 calculateCachedEnd();
2171 return mCachedEnd;
2174 void prependToSpan(View view) {
2175 LayoutParams lp = getLayoutParams(view);
2176 lp.mSpan = this;
2177 mViews.add(0, view);
2178 mCachedStart = INVALID_LINE;
2179 if (mViews.size() == 1) {
2180 mCachedEnd = INVALID_LINE;
2182 if (lp.isItemRemoved() || lp.isItemChanged()) {
2183 mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2187 void appendToSpan(View view) {
2188 LayoutParams lp = getLayoutParams(view);
2189 lp.mSpan = this;
2190 mViews.add(view);
2191 mCachedEnd = INVALID_LINE;
2192 if (mViews.size() == 1) {
2193 mCachedStart = INVALID_LINE;
2195 if (lp.isItemRemoved() || lp.isItemChanged()) {
2196 mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
2200 // Useful method to preserve positions on a re-layout.
2201 void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
2202 int reference;
2203 if (reverseLayout) {
2204 reference = getEndLine(INVALID_LINE);
2205 } else {
2206 reference = getStartLine(INVALID_LINE);
2208 clear();
2209 if (reference == INVALID_LINE) {
2210 return;
2212 if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
2213 (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
2214 return;
2216 if (offset != INVALID_OFFSET) {
2217 reference += offset;
2219 mCachedStart = mCachedEnd = reference;
2222 void clear() {
2223 mViews.clear();
2224 invalidateCache();
2225 mDeletedSize = 0;
2228 void invalidateCache() {
2229 mCachedStart = INVALID_LINE;
2230 mCachedEnd = INVALID_LINE;
2233 void setLine(int line) {
2234 mCachedEnd = mCachedStart = line;
2237 void popEnd() {
2238 final int size = mViews.size();
2239 View end = mViews.remove(size - 1);
2240 final LayoutParams lp = getLayoutParams(end);
2241 lp.mSpan = null;
2242 if (lp.isItemRemoved() || lp.isItemChanged()) {
2243 mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
2245 if (size == 1) {
2246 mCachedStart = INVALID_LINE;
2248 mCachedEnd = INVALID_LINE;
2251 void popStart() {
2252 View start = mViews.remove(0);
2253 final LayoutParams lp = getLayoutParams(start);
2254 lp.mSpan = null;
2255 if (mViews.size() == 0) {
2256 mCachedEnd = INVALID_LINE;
2258 if (lp.isItemRemoved() || lp.isItemChanged()) {
2259 mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
2261 mCachedStart = INVALID_LINE;
2264 public int getDeletedSize() {
2265 return mDeletedSize;
2268 LayoutParams getLayoutParams(View view) {
2269 return (LayoutParams) view.getLayoutParams();
2272 void onOffset(int dt) {
2273 if (mCachedStart != INVALID_LINE) {
2274 mCachedStart += dt;
2276 if (mCachedEnd != INVALID_LINE) {
2277 mCachedEnd += dt;
2281 // normalized offset is how much this span can scroll
2282 int getNormalizedOffset(int dt, int targetStart, int targetEnd) {
2283 if (mViews.size() == 0) {
2284 return 0;
2286 if (dt < 0) {
2287 final int endSpace = getEndLine() - targetEnd;
2288 if (endSpace <= 0) {
2289 return 0;
2291 return -dt > endSpace ? -endSpace : dt;
2292 } else {
2293 final int startSpace = targetStart - getStartLine();
2294 if (startSpace <= 0) {
2295 return 0;
2297 return startSpace < dt ? startSpace : dt;
2302 * Returns if there is no child between start-end lines
2304 * @param start The start line
2305 * @param end The end line
2306 * @return true if a new child can be added between start and end
2308 boolean isEmpty(int start, int end) {
2309 final int count = mViews.size();
2310 for (int i = 0; i < count; i++) {
2311 final View view = mViews.get(i);
2312 if (mPrimaryOrientation.getDecoratedStart(view) < end &&
2313 mPrimaryOrientation.getDecoratedEnd(view) > start) {
2314 return false;
2317 return true;
2320 public int findFirstVisibleItemPosition() {
2321 return mReverseLayout
2322 ? findOneVisibleChild(mViews.size() - 1, -1, false)
2323 : findOneVisibleChild(0, mViews.size(), false);
2326 public int findFirstCompletelyVisibleItemPosition() {
2327 return mReverseLayout
2328 ? findOneVisibleChild(mViews.size() - 1, -1, true)
2329 : findOneVisibleChild(0, mViews.size(), true);
2332 public int findLastVisibleItemPosition() {
2333 return mReverseLayout
2334 ? findOneVisibleChild(0, mViews.size(), false)
2335 : findOneVisibleChild(mViews.size() - 1, -1, false);
2338 public int findLastCompletelyVisibleItemPosition() {
2339 return mReverseLayout
2340 ? findOneVisibleChild(0, mViews.size(), true)
2341 : findOneVisibleChild(mViews.size() - 1, -1, true);
2344 int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
2345 final int start = mPrimaryOrientation.getStartAfterPadding();
2346 final int end = mPrimaryOrientation.getEndAfterPadding();
2347 final int next = toIndex > fromIndex ? 1 : -1;
2348 for (int i = fromIndex; i != toIndex; i += next) {
2349 final View child = mViews.get(i);
2350 final int childStart = mPrimaryOrientation.getDecoratedStart(child);
2351 final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
2352 if (childStart < end && childEnd > start) {
2353 if (completelyVisible) {
2354 if (childStart >= start && childEnd <= end) {
2355 return getPosition(child);
2357 } else {
2358 return getPosition(child);
2362 return NO_POSITION;
2367 * An array of mappings from adapter position to span.
2368 * This only grows when a write happens and it grows up to the size of the adapter.
2370 static class LazySpanLookup {
2372 private static final int MIN_SIZE = 10;
2373 int[] mData;
2374 List<FullSpanItem> mFullSpanItems;
2378 * Invalidates everything after this position, including full span information
2380 int forceInvalidateAfter(int position) {
2381 if (mFullSpanItems != null) {
2382 for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2383 FullSpanItem fsi = mFullSpanItems.get(i);
2384 if (fsi.mPosition >= position) {
2385 mFullSpanItems.remove(i);
2389 return invalidateAfter(position);
2393 * returns end position for invalidation.
2395 int invalidateAfter(int position) {
2396 if (mData == null) {
2397 return RecyclerView.NO_POSITION;
2399 if (position >= mData.length) {
2400 return RecyclerView.NO_POSITION;
2402 int endPosition = invalidateFullSpansAfter(position);
2403 if (endPosition == RecyclerView.NO_POSITION) {
2404 Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
2405 return mData.length;
2406 } else {
2407 // just invalidate items in between
2408 Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID);
2409 return endPosition + 1;
2413 int getSpan(int position) {
2414 if (mData == null || position >= mData.length) {
2415 return LayoutParams.INVALID_SPAN_ID;
2416 } else {
2417 return mData[position];
2421 void setSpan(int position, Span span) {
2422 ensureSize(position);
2423 mData[position] = span.mIndex;
2426 int sizeForPosition(int position) {
2427 int len = mData.length;
2428 while (len <= position) {
2429 len *= 2;
2431 return len;
2434 void ensureSize(int position) {
2435 if (mData == null) {
2436 mData = new int[Math.max(position, MIN_SIZE) + 1];
2437 Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2438 } else if (position >= mData.length) {
2439 int[] old = mData;
2440 mData = new int[sizeForPosition(position)];
2441 System.arraycopy(old, 0, mData, 0, old.length);
2442 Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
2446 void clear() {
2447 if (mData != null) {
2448 Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
2450 mFullSpanItems = null;
2453 void offsetForRemoval(int positionStart, int itemCount) {
2454 if (mData == null || positionStart >= mData.length) {
2455 return;
2457 ensureSize(positionStart + itemCount);
2458 System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
2459 mData.length - positionStart - itemCount);
2460 Arrays.fill(mData, mData.length - itemCount, mData.length,
2461 LayoutParams.INVALID_SPAN_ID);
2462 offsetFullSpansForRemoval(positionStart, itemCount);
2465 private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
2466 if (mFullSpanItems == null) {
2467 return;
2469 final int end = positionStart + itemCount;
2470 for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2471 FullSpanItem fsi = mFullSpanItems.get(i);
2472 if (fsi.mPosition < positionStart) {
2473 continue;
2475 if (fsi.mPosition < end) {
2476 mFullSpanItems.remove(i);
2477 } else {
2478 fsi.mPosition -= itemCount;
2483 void offsetForAddition(int positionStart, int itemCount) {
2484 if (mData == null || positionStart >= mData.length) {
2485 return;
2487 ensureSize(positionStart + itemCount);
2488 System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
2489 mData.length - positionStart - itemCount);
2490 Arrays.fill(mData, positionStart, positionStart + itemCount,
2491 LayoutParams.INVALID_SPAN_ID);
2492 offsetFullSpansForAddition(positionStart, itemCount);
2495 private void offsetFullSpansForAddition(int positionStart, int itemCount) {
2496 if (mFullSpanItems == null) {
2497 return;
2499 for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2500 FullSpanItem fsi = mFullSpanItems.get(i);
2501 if (fsi.mPosition < positionStart) {
2502 continue;
2504 fsi.mPosition += itemCount;
2509 * Returns when invalidation should end. e.g. hitting a full span position.
2510 * Returned position SHOULD BE invalidated.
2512 private int invalidateFullSpansAfter(int position) {
2513 if (mFullSpanItems == null) {
2514 return RecyclerView.NO_POSITION;
2516 final FullSpanItem item = getFullSpanItem(position);
2517 // if there is an fsi at this position, get rid of it.
2518 if (item != null) {
2519 mFullSpanItems.remove(item);
2521 int nextFsiIndex = -1;
2522 final int count = mFullSpanItems.size();
2523 for (int i = 0; i < count; i++) {
2524 FullSpanItem fsi = mFullSpanItems.get(i);
2525 if (fsi.mPosition >= position) {
2526 nextFsiIndex = i;
2527 break;
2530 if (nextFsiIndex != -1) {
2531 FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
2532 mFullSpanItems.remove(nextFsiIndex);
2533 return fsi.mPosition;
2535 return RecyclerView.NO_POSITION;
2538 public void addFullSpanItem(FullSpanItem fullSpanItem) {
2539 if (mFullSpanItems == null) {
2540 mFullSpanItems = new ArrayList<FullSpanItem>();
2542 final int size = mFullSpanItems.size();
2543 for (int i = 0; i < size; i++) {
2544 FullSpanItem other = mFullSpanItems.get(i);
2545 if (other.mPosition == fullSpanItem.mPosition) {
2546 if (DEBUG) {
2547 throw new IllegalStateException("two fsis for same position");
2548 } else {
2549 mFullSpanItems.remove(i);
2552 if (other.mPosition >= fullSpanItem.mPosition) {
2553 mFullSpanItems.add(i, fullSpanItem);
2554 return;
2557 // if it is not added to a position.
2558 mFullSpanItems.add(fullSpanItem);
2561 public FullSpanItem getFullSpanItem(int position) {
2562 if (mFullSpanItems == null) {
2563 return null;
2565 for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
2566 final FullSpanItem fsi = mFullSpanItems.get(i);
2567 if (fsi.mPosition == position) {
2568 return fsi;
2571 return null;
2575 * @param minPos inclusive
2576 * @param maxPos exclusive
2577 * @param gapDir if not 0, returns FSIs on in that direction
2578 * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
2579 * returned even if its gap direction does not match.
2581 public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
2582 boolean hasUnwantedGapAfter) {
2583 if (mFullSpanItems == null) {
2584 return null;
2586 final int limit = mFullSpanItems.size();
2587 for (int i = 0; i < limit; i++) {
2588 FullSpanItem fsi = mFullSpanItems.get(i);
2589 if (fsi.mPosition >= maxPos) {
2590 return null;
2592 if (fsi.mPosition >= minPos
2593 && (gapDir == 0 || fsi.mGapDir == gapDir ||
2594 (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
2595 return fsi;
2598 return null;
2602 * We keep information about full span items because they may create gaps in the UI.
2604 static class FullSpanItem implements Parcelable {
2606 int mPosition;
2607 int mGapDir;
2608 int[] mGapPerSpan;
2609 // A full span may be laid out in primary direction but may have gaps due to
2610 // invalidation of views after it. This is recorded during a reverse scroll and if
2611 // view is still on the screen after scroll stops, we have to recalculate layout
2612 boolean mHasUnwantedGapAfter;
2614 public FullSpanItem(Parcel in) {
2615 mPosition = in.readInt();
2616 mGapDir = in.readInt();
2617 mHasUnwantedGapAfter = in.readInt() == 1;
2618 int spanCount = in.readInt();
2619 if (spanCount > 0) {
2620 mGapPerSpan = new int[spanCount];
2621 in.readIntArray(mGapPerSpan);
2625 public FullSpanItem() {
2628 int getGapForSpan(int spanIndex) {
2629 return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
2632 public void invalidateSpanGaps() {
2633 mGapPerSpan = null;
2636 @Override
2637 public int describeContents() {
2638 return 0;
2641 @Override
2642 public void writeToParcel(Parcel dest, int flags) {
2643 dest.writeInt(mPosition);
2644 dest.writeInt(mGapDir);
2645 dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
2646 if (mGapPerSpan != null && mGapPerSpan.length > 0) {
2647 dest.writeInt(mGapPerSpan.length);
2648 dest.writeIntArray(mGapPerSpan);
2649 } else {
2650 dest.writeInt(0);
2654 @Override
2655 public String toString() {
2656 return "FullSpanItem{" +
2657 "mPosition=" + mPosition +
2658 ", mGapDir=" + mGapDir +
2659 ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter +
2660 ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
2661 '}';
2664 public static final Parcelable.Creator<FullSpanItem> CREATOR
2665 = new Parcelable.Creator<FullSpanItem>() {
2666 @Override
2667 public FullSpanItem createFromParcel(Parcel in) {
2668 return new FullSpanItem(in);
2671 @Override
2672 public FullSpanItem[] newArray(int size) {
2673 return new FullSpanItem[size];
2679 static class SavedState implements Parcelable {
2681 int mAnchorPosition;
2682 int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
2683 int mSpanOffsetsSize;
2684 int[] mSpanOffsets;
2685 int mSpanLookupSize;
2686 int[] mSpanLookup;
2687 List<LazySpanLookup.FullSpanItem> mFullSpanItems;
2688 boolean mReverseLayout;
2689 boolean mAnchorLayoutFromEnd;
2690 boolean mLastLayoutRTL;
2692 public SavedState() {
2695 SavedState(Parcel in) {
2696 mAnchorPosition = in.readInt();
2697 mVisibleAnchorPosition = in.readInt();
2698 mSpanOffsetsSize = in.readInt();
2699 if (mSpanOffsetsSize > 0) {
2700 mSpanOffsets = new int[mSpanOffsetsSize];
2701 in.readIntArray(mSpanOffsets);
2704 mSpanLookupSize = in.readInt();
2705 if (mSpanLookupSize > 0) {
2706 mSpanLookup = new int[mSpanLookupSize];
2707 in.readIntArray(mSpanLookup);
2709 mReverseLayout = in.readInt() == 1;
2710 mAnchorLayoutFromEnd = in.readInt() == 1;
2711 mLastLayoutRTL = in.readInt() == 1;
2712 mFullSpanItems = in.readArrayList(
2713 LazySpanLookup.FullSpanItem.class.getClassLoader());
2716 public SavedState(SavedState other) {
2717 mSpanOffsetsSize = other.mSpanOffsetsSize;
2718 mAnchorPosition = other.mAnchorPosition;
2719 mVisibleAnchorPosition = other.mVisibleAnchorPosition;
2720 mSpanOffsets = other.mSpanOffsets;
2721 mSpanLookupSize = other.mSpanLookupSize;
2722 mSpanLookup = other.mSpanLookup;
2723 mReverseLayout = other.mReverseLayout;
2724 mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
2725 mLastLayoutRTL = other.mLastLayoutRTL;
2726 mFullSpanItems = other.mFullSpanItems;
2729 void invalidateSpanInfo() {
2730 mSpanOffsets = null;
2731 mSpanOffsetsSize = 0;
2732 mSpanLookupSize = 0;
2733 mSpanLookup = null;
2734 mFullSpanItems = null;
2737 void invalidateAnchorPositionInfo() {
2738 mSpanOffsets = null;
2739 mSpanOffsetsSize = 0;
2740 mAnchorPosition = NO_POSITION;
2741 mVisibleAnchorPosition = NO_POSITION;
2744 @Override
2745 public int describeContents() {
2746 return 0;
2749 @Override
2750 public void writeToParcel(Parcel dest, int flags) {
2751 dest.writeInt(mAnchorPosition);
2752 dest.writeInt(mVisibleAnchorPosition);
2753 dest.writeInt(mSpanOffsetsSize);
2754 if (mSpanOffsetsSize > 0) {
2755 dest.writeIntArray(mSpanOffsets);
2757 dest.writeInt(mSpanLookupSize);
2758 if (mSpanLookupSize > 0) {
2759 dest.writeIntArray(mSpanLookup);
2761 dest.writeInt(mReverseLayout ? 1 : 0);
2762 dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
2763 dest.writeInt(mLastLayoutRTL ? 1 : 0);
2764 dest.writeList(mFullSpanItems);
2767 public static final Parcelable.Creator<SavedState> CREATOR
2768 = new Parcelable.Creator<SavedState>() {
2769 @Override
2770 public SavedState createFromParcel(Parcel in) {
2771 return new SavedState(in);
2774 @Override
2775 public SavedState[] newArray(int size) {
2776 return new SavedState[size];
2782 * Data class to hold the information about an anchor position which is used in onLayout call.
2784 private class AnchorInfo {
2786 int mPosition;
2787 int mOffset;
2788 boolean mLayoutFromEnd;
2789 boolean mInvalidateOffsets;
2791 void reset() {
2792 mPosition = NO_POSITION;
2793 mOffset = INVALID_OFFSET;
2794 mLayoutFromEnd = false;
2795 mInvalidateOffsets = false;
2798 void assignCoordinateFromPadding() {
2799 mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
2800 : mPrimaryOrientation.getStartAfterPadding();
2803 void assignCoordinateFromPadding(int addedDistance) {
2804 if (mLayoutFromEnd) {
2805 mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
2806 } else {
2807 mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;