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