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 17 package androidx.recyclerview.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.graphics.PointF; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.accessibility.AccessibilityEvent; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.RestrictTo; 33 import androidx.core.os.TraceCompat; 34 import androidx.core.view.ViewCompat; 35 36 import java.util.List; 37 38 /** 39 * A {@link RecyclerView.LayoutManager} implementation which provides 40 * similar functionality to {@link android.widget.ListView}. 41 */ 42 public class LinearLayoutManager extends RecyclerView.LayoutManager implements 43 ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider { 44 45 private static final String TAG = "LinearLayoutManager"; 46 47 static final boolean DEBUG = false; 48 49 public static final int HORIZONTAL = RecyclerView.HORIZONTAL; 50 51 public static final int VERTICAL = RecyclerView.VERTICAL; 52 53 public static final int INVALID_OFFSET = Integer.MIN_VALUE; 54 55 56 /** 57 * While trying to find next view to focus, LayoutManager will not try to scroll more 58 * than this factor times the total space of the list. If layout is vertical, total space is the 59 * height minus padding, if layout is horizontal, total space is the width minus padding. 60 */ 61 private static final float MAX_SCROLL_FACTOR = 1 / 3f; 62 63 /** 64 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} 65 */ 66 @RecyclerView.Orientation 67 int mOrientation = RecyclerView.DEFAULT_ORIENTATION; 68 69 /** 70 * Helper class that keeps temporary layout state. 71 * It does not keep state after layout is complete but we still keep a reference to re-use 72 * the same object. 73 */ 74 private LayoutState mLayoutState; 75 76 /** 77 * Many calculations are made depending on orientation. To keep it clean, this interface 78 * helps {@link LinearLayoutManager} make those decisions. 79 */ 80 OrientationHelper mOrientationHelper; 81 82 /** 83 * We need to track this so that we can ignore current position when it changes. 84 */ 85 private boolean mLastStackFromEnd; 86 87 88 /** 89 * Defines if layout should be calculated from end to start. 90 * 91 * @see #mShouldReverseLayout 92 */ 93 private boolean mReverseLayout = false; 94 95 /** 96 * This keeps the final value for how LayoutManager should start laying out views. 97 * It is calculated by checking {@link #getReverseLayout()} and View's layout direction. 98 * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run. 99 */ 100 boolean mShouldReverseLayout = false; 101 102 /** 103 * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and 104 * it supports both orientations. 105 * see {@link android.widget.AbsListView#setStackFromBottom(boolean)} 106 */ 107 private boolean mStackFromEnd = false; 108 109 /** 110 * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. 111 * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} 112 */ 113 private boolean mSmoothScrollbarEnabled = true; 114 115 /** 116 * When LayoutManager needs to scroll to a position, it sets this variable and requests a 117 * layout which will check this variable and re-layout accordingly. 118 */ 119 int mPendingScrollPosition = RecyclerView.NO_POSITION; 120 121 /** 122 * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is 123 * called. 124 */ 125 int mPendingScrollPositionOffset = INVALID_OFFSET; 126 127 private boolean mRecycleChildrenOnDetach; 128 129 SavedState mPendingSavedState = null; 130 131 /** 132 * Re-used variable to keep anchor information on re-layout. 133 * Anchor position and coordinate defines the reference point for LLM while doing a layout. 134 * */ 135 final AnchorInfo mAnchorInfo = new AnchorInfo(); 136 137 /** 138 * Stashed to avoid allocation, currently only used in #fill() 139 */ 140 private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult(); 141 142 /** 143 * Number of items to prefetch when first coming on screen with new data. 144 */ 145 private int mInitialPrefetchItemCount = 2; 146 147 /** 148 * Creates a vertical LinearLayoutManager 149 * 150 * @param context Current context, will be used to access resources. 151 */ 152 public LinearLayoutManager(Context context) { 153 this(context, RecyclerView.DEFAULT_ORIENTATION, false); 154 } 155 156 /** 157 * @param context Current context, will be used to access resources. 158 * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link 159 * #VERTICAL}. 160 * @param reverseLayout When set to true, layouts from end to start. 161 */ 162 public LinearLayoutManager(Context context, @RecyclerView.Orientation int orientation, 163 boolean reverseLayout) { 164 setOrientation(orientation); 165 setReverseLayout(reverseLayout); 166 } 167 168 /** 169 * Constructor used when layout manager is set in XML by RecyclerView attribute 170 * "layoutManager". Defaults to vertical orientation. 171 * 172 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation 173 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout 174 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd 175 */ 176 public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, 177 int defStyleRes) { 178 Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); 179 setOrientation(properties.orientation); 180 setReverseLayout(properties.reverseLayout); 181 setStackFromEnd(properties.stackFromEnd); 182 } 183 184 @Override 185 public boolean isAutoMeasureEnabled() { 186 return true; 187 } 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override 193 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 194 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 195 ViewGroup.LayoutParams.WRAP_CONTENT); 196 } 197 198 /** 199 * Returns whether LayoutManager will recycle its children when it is detached from 200 * RecyclerView. 201 * 202 * @return true if LayoutManager will recycle its children when it is detached from 203 * RecyclerView. 204 */ 205 public boolean getRecycleChildrenOnDetach() { 206 return mRecycleChildrenOnDetach; 207 } 208 209 /** 210 * Set whether LayoutManager will recycle its children when it is detached from 211 * RecyclerView. 212 * <p> 213 * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set 214 * this flag to <code>true</code> so that views will be available to other RecyclerViews 215 * immediately. 216 * <p> 217 * Note that, setting this flag will result in a performance drop if RecyclerView 218 * is restored. 219 * 220 * @param recycleChildrenOnDetach Whether children should be recycled in detach or not. 221 */ 222 public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) { 223 mRecycleChildrenOnDetach = recycleChildrenOnDetach; 224 } 225 226 @Override 227 public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { 228 super.onDetachedFromWindow(view, recycler); 229 if (mRecycleChildrenOnDetach) { 230 removeAndRecycleAllViews(recycler); 231 recycler.clear(); 232 } 233 } 234 235 @Override 236 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 237 super.onInitializeAccessibilityEvent(event); 238 if (getChildCount() > 0) { 239 event.setFromIndex(findFirstVisibleItemPosition()); 240 event.setToIndex(findLastVisibleItemPosition()); 241 } 242 } 243 244 @Override 245 public Parcelable onSaveInstanceState() { 246 if (mPendingSavedState != null) { 247 return new SavedState(mPendingSavedState); 248 } 249 SavedState state = new SavedState(); 250 if (getChildCount() > 0) { 251 ensureLayoutState(); 252 boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout; 253 state.mAnchorLayoutFromEnd = didLayoutFromEnd; 254 if (didLayoutFromEnd) { 255 final View refChild = getChildClosestToEnd(); 256 state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() 257 - mOrientationHelper.getDecoratedEnd(refChild); 258 state.mAnchorPosition = getPosition(refChild); 259 } else { 260 final View refChild = getChildClosestToStart(); 261 state.mAnchorPosition = getPosition(refChild); 262 state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) 263 - mOrientationHelper.getStartAfterPadding(); 264 } 265 } else { 266 state.invalidateAnchor(); 267 } 268 return state; 269 } 270 271 @Override 272 public void onRestoreInstanceState(Parcelable state) { 273 if (state instanceof SavedState) { 274 mPendingSavedState = (SavedState) state; 275 requestLayout(); 276 if (DEBUG) { 277 Log.d(TAG, "loaded saved state"); 278 } 279 } else if (DEBUG) { 280 Log.d(TAG, "invalid saved state class"); 281 } 282 } 283 284 /** 285 * @return true if {@link #getOrientation()} is {@link #HORIZONTAL} 286 */ 287 @Override 288 public boolean canScrollHorizontally() { 289 return mOrientation == HORIZONTAL; 290 } 291 292 /** 293 * @return true if {@link #getOrientation()} is {@link #VERTICAL} 294 */ 295 @Override 296 public boolean canScrollVertically() { 297 return mOrientation == VERTICAL; 298 } 299 300 /** 301 * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)} 302 */ 303 public void setStackFromEnd(boolean stackFromEnd) { 304 assertNotInLayoutOrScroll(null); 305 if (mStackFromEnd == stackFromEnd) { 306 return; 307 } 308 mStackFromEnd = stackFromEnd; 309 requestLayout(); 310 } 311 312 public boolean getStackFromEnd() { 313 return mStackFromEnd; 314 } 315 316 /** 317 * Returns the current orientation of the layout. 318 * 319 * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL} 320 * @see #setOrientation(int) 321 */ 322 @RecyclerView.Orientation 323 public int getOrientation() { 324 return mOrientation; 325 } 326 327 /** 328 * Sets the orientation of the layout. {@link LinearLayoutManager} 329 * will do its best to keep scroll position. 330 * 331 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} 332 */ 333 public void setOrientation(@RecyclerView.Orientation int orientation) { 334 if (orientation != HORIZONTAL && orientation != VERTICAL) { 335 throw new IllegalArgumentException("invalid orientation:" + orientation); 336 } 337 338 assertNotInLayoutOrScroll(null); 339 340 if (orientation != mOrientation || mOrientationHelper == null) { 341 mOrientationHelper = 342 OrientationHelper.createOrientationHelper(this, orientation); 343 mAnchorInfo.mOrientationHelper = mOrientationHelper; 344 mOrientation = orientation; 345 requestLayout(); 346 } 347 } 348 349 /** 350 * Calculates the view layout order. (e.g. from end to start or start to end) 351 * RTL layout support is applied automatically. So if layout is RTL and 352 * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. 353 */ 354 private void resolveShouldLayoutReverse() { 355 // A == B is the same result, but we rather keep it readable 356 if (mOrientation == VERTICAL || !isLayoutRTL()) { 357 mShouldReverseLayout = mReverseLayout; 358 } else { 359 mShouldReverseLayout = !mReverseLayout; 360 } 361 } 362 363 /** 364 * Returns if views are laid out from the opposite direction of the layout. 365 * 366 * @return If layout is reversed or not. 367 * @see #setReverseLayout(boolean) 368 */ 369 public boolean getReverseLayout() { 370 return mReverseLayout; 371 } 372 373 /** 374 * Used to reverse item traversal and layout order. 375 * This behaves similar to the layout change for RTL views. When set to true, first item is 376 * laid out at the end of the UI, second item is laid out before it etc. 377 * 378 * For horizontal layouts, it depends on the layout direction. 379 * When set to true, If {@link RecyclerView} is LTR, than it will 380 * layout from RTL, if {@link RecyclerView}} is RTL, it will layout 381 * from LTR. 382 * 383 * If you are looking for the exact same behavior of 384 * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use 385 * {@link #setStackFromEnd(boolean)} 386 */ 387 public void setReverseLayout(boolean reverseLayout) { 388 assertNotInLayoutOrScroll(null); 389 if (reverseLayout == mReverseLayout) { 390 return; 391 } 392 mReverseLayout = reverseLayout; 393 requestLayout(); 394 } 395 396 /** 397 * {@inheritDoc} 398 */ 399 @Override 400 public View findViewByPosition(int position) { 401 final int childCount = getChildCount(); 402 if (childCount == 0) { 403 return null; 404 } 405 final int firstChild = getPosition(getChildAt(0)); 406 final int viewPosition = position - firstChild; 407 if (viewPosition >= 0 && viewPosition < childCount) { 408 final View child = getChildAt(viewPosition); 409 if (getPosition(child) == position) { 410 return child; // in pre-layout, this may not match 411 } 412 } 413 // fallback to traversal. This might be necessary in pre-layout. 414 return super.findViewByPosition(position); 415 } 416 417 /** 418 * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p> 419 * 420 * <p>By default, {@link LinearLayoutManager} lays out 1 extra page 421 * of items while smooth scrolling and 0 otherwise. You can override this method to implement 422 * your custom layout pre-cache logic.</p> 423 * 424 * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant 425 * performance cost. It's typically only desirable in places like smooth scrolling to an unknown 426 * location, where 1) the extra content helps LinearLayoutManager know in advance when its 427 * target is approaching, so it can decelerate early and smoothly and 2) while motion is 428 * continuous.</p> 429 * 430 * <p>Extending the extra layout space is especially expensive if done while the user may change 431 * scrolling direction. Changing direction will cause the extra layout space to swap to the 432 * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large 433 * enough to handle it.</p> 434 * 435 * @return The extra space that should be laid out (in pixels). 436 */ 437 protected int getExtraLayoutSpace(RecyclerView.State state) { 438 if (state.hasTargetScrollPosition()) { 439 return mOrientationHelper.getTotalSpace(); 440 } else { 441 return 0; 442 } 443 } 444 445 @Override 446 public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, 447 int position) { 448 LinearSmoothScroller linearSmoothScroller = 449 new LinearSmoothScroller(recyclerView.getContext()); 450 linearSmoothScroller.setTargetPosition(position); 451 startSmoothScroll(linearSmoothScroller); 452 } 453 454 @Override 455 public PointF computeScrollVectorForPosition(int targetPosition) { 456 if (getChildCount() == 0) { 457 return null; 458 } 459 final int firstChildPos = getPosition(getChildAt(0)); 460 final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1; 461 if (mOrientation == HORIZONTAL) { 462 return new PointF(direction, 0); 463 } else { 464 return new PointF(0, direction); 465 } 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override 472 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 473 // layout algorithm: 474 // 1) by checking children and other variables, find an anchor coordinate and an anchor 475 // item position. 476 // 2) fill towards start, stacking from bottom 477 // 3) fill towards end, stacking from top 478 // 4) scroll to fulfill requirements like stack from bottom. 479 // create layout state 480 if (DEBUG) { 481 Log.d(TAG, "is pre layout:" + state.isPreLayout()); 482 } 483 if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) { 484 if (state.getItemCount() == 0) { 485 removeAndRecycleAllViews(recycler); 486 return; 487 } 488 } 489 if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { 490 mPendingScrollPosition = mPendingSavedState.mAnchorPosition; 491 } 492 493 ensureLayoutState(); 494 mLayoutState.mRecycle = false; 495 // resolve layout direction 496 resolveShouldLayoutReverse(); 497 498 final View focused = getFocusedChild(); 499 if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION 500 || mPendingSavedState != null) { 501 mAnchorInfo.reset(); 502 mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; 503 // calculate anchor position and coordinate 504 updateAnchorInfoForLayout(recycler, state, mAnchorInfo); 505 mAnchorInfo.mValid = true; 506 } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused) 507 >= mOrientationHelper.getEndAfterPadding() 508 || mOrientationHelper.getDecoratedEnd(focused) 509 <= mOrientationHelper.getStartAfterPadding())) { 510 // This case relates to when the anchor child is the focused view and due to layout 511 // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows 512 // up after tapping an EditText which shrinks RV causing the focused view (The tapped 513 // EditText which is the anchor child) to get kicked out of the screen. Will update the 514 // anchor coordinate in order to make sure that the focused view is laid out. Otherwise, 515 // the available space in layoutState will be calculated as negative preventing the 516 // focused view from being laid out in fill. 517 // Note that we won't update the anchor position between layout passes (refer to 518 // TestResizingRelayoutWithAutoMeasure), which happens if we were to call 519 // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference 520 // child which can change between layout passes). 521 mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); 522 } 523 if (DEBUG) { 524 Log.d(TAG, "Anchor info:" + mAnchorInfo); 525 } 526 527 // LLM may decide to layout items for "extra" pixels to account for scrolling target, 528 // caching or predictive animations. 529 int extraForStart; 530 int extraForEnd; 531 final int extra = getExtraLayoutSpace(state); 532 // If the previous scroll delta was less than zero, the extra space should be laid out 533 // at the start. Otherwise, it should be at the end. 534 if (mLayoutState.mLastScrollDelta >= 0) { 535 extraForEnd = extra; 536 extraForStart = 0; 537 } else { 538 extraForStart = extra; 539 extraForEnd = 0; 540 } 541 extraForStart += mOrientationHelper.getStartAfterPadding(); 542 extraForEnd += mOrientationHelper.getEndPadding(); 543 if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION 544 && mPendingScrollPositionOffset != INVALID_OFFSET) { 545 // if the child is visible and we are going to move it around, we should layout 546 // extra items in the opposite direction to make sure new items animate nicely 547 // instead of just fading in 548 final View existing = findViewByPosition(mPendingScrollPosition); 549 if (existing != null) { 550 final int current; 551 final int upcomingOffset; 552 if (mShouldReverseLayout) { 553 current = mOrientationHelper.getEndAfterPadding() 554 - mOrientationHelper.getDecoratedEnd(existing); 555 upcomingOffset = current - mPendingScrollPositionOffset; 556 } else { 557 current = mOrientationHelper.getDecoratedStart(existing) 558 - mOrientationHelper.getStartAfterPadding(); 559 upcomingOffset = mPendingScrollPositionOffset - current; 560 } 561 if (upcomingOffset > 0) { 562 extraForStart += upcomingOffset; 563 } else { 564 extraForEnd -= upcomingOffset; 565 } 566 } 567 } 568 int startOffset; 569 int endOffset; 570 final int firstLayoutDirection; 571 if (mAnchorInfo.mLayoutFromEnd) { 572 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL 573 : LayoutState.ITEM_DIRECTION_HEAD; 574 } else { 575 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD 576 : LayoutState.ITEM_DIRECTION_TAIL; 577 } 578 579 onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); 580 detachAndScrapAttachedViews(recycler); 581 mLayoutState.mInfinite = resolveIsInfinite(); 582 mLayoutState.mIsPreLayout = state.isPreLayout(); 583 if (mAnchorInfo.mLayoutFromEnd) { 584 // fill towards start 585 updateLayoutStateToFillStart(mAnchorInfo); 586 mLayoutState.mExtra = extraForStart; 587 fill(recycler, mLayoutState, state, false); 588 startOffset = mLayoutState.mOffset; 589 final int firstElement = mLayoutState.mCurrentPosition; 590 if (mLayoutState.mAvailable > 0) { 591 extraForEnd += mLayoutState.mAvailable; 592 } 593 // fill towards end 594 updateLayoutStateToFillEnd(mAnchorInfo); 595 mLayoutState.mExtra = extraForEnd; 596 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; 597 fill(recycler, mLayoutState, state, false); 598 endOffset = mLayoutState.mOffset; 599 600 if (mLayoutState.mAvailable > 0) { 601 // end could not consume all. add more items towards start 602 extraForStart = mLayoutState.mAvailable; 603 updateLayoutStateToFillStart(firstElement, startOffset); 604 mLayoutState.mExtra = extraForStart; 605 fill(recycler, mLayoutState, state, false); 606 startOffset = mLayoutState.mOffset; 607 } 608 } else { 609 // fill towards end 610 updateLayoutStateToFillEnd(mAnchorInfo); 611 mLayoutState.mExtra = extraForEnd; 612 fill(recycler, mLayoutState, state, false); 613 endOffset = mLayoutState.mOffset; 614 final int lastElement = mLayoutState.mCurrentPosition; 615 if (mLayoutState.mAvailable > 0) { 616 extraForStart += mLayoutState.mAvailable; 617 } 618 // fill towards start 619 updateLayoutStateToFillStart(mAnchorInfo); 620 mLayoutState.mExtra = extraForStart; 621 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; 622 fill(recycler, mLayoutState, state, false); 623 startOffset = mLayoutState.mOffset; 624 625 if (mLayoutState.mAvailable > 0) { 626 extraForEnd = mLayoutState.mAvailable; 627 // start could not consume all it should. add more items towards end 628 updateLayoutStateToFillEnd(lastElement, endOffset); 629 mLayoutState.mExtra = extraForEnd; 630 fill(recycler, mLayoutState, state, false); 631 endOffset = mLayoutState.mOffset; 632 } 633 } 634 635 // changes may cause gaps on the UI, try to fix them. 636 // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have 637 // changed 638 if (getChildCount() > 0) { 639 // because layout from end may be changed by scroll to position 640 // we re-calculate it. 641 // find which side we should check for gaps. 642 if (mShouldReverseLayout ^ mStackFromEnd) { 643 int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); 644 startOffset += fixOffset; 645 endOffset += fixOffset; 646 fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); 647 startOffset += fixOffset; 648 endOffset += fixOffset; 649 } else { 650 int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); 651 startOffset += fixOffset; 652 endOffset += fixOffset; 653 fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); 654 startOffset += fixOffset; 655 endOffset += fixOffset; 656 } 657 } 658 layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); 659 if (!state.isPreLayout()) { 660 mOrientationHelper.onLayoutComplete(); 661 } else { 662 mAnchorInfo.reset(); 663 } 664 mLastStackFromEnd = mStackFromEnd; 665 if (DEBUG) { 666 validateChildOrder(); 667 } 668 } 669 670 @Override 671 public void onLayoutCompleted(RecyclerView.State state) { 672 super.onLayoutCompleted(state); 673 mPendingSavedState = null; // we don't need this anymore 674 mPendingScrollPosition = RecyclerView.NO_POSITION; 675 mPendingScrollPositionOffset = INVALID_OFFSET; 676 mAnchorInfo.reset(); 677 } 678 679 /** 680 * Method called when Anchor position is decided. Extending class can setup accordingly or 681 * even update anchor info if necessary. 682 * @param recycler The recycler for the layout 683 * @param state The layout state 684 * @param anchorInfo The mutable POJO that keeps the position and offset. 685 * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter 686 * indices. 687 */ 688 void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, 689 AnchorInfo anchorInfo, int firstLayoutItemDirection) { 690 } 691 692 /** 693 * If necessary, layouts new items for predictive animations 694 */ 695 private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler, 696 RecyclerView.State state, int startOffset, 697 int endOffset) { 698 // If there are scrap children that we did not layout, we need to find where they did go 699 // and layout them accordingly so that animations can work as expected. 700 // This case may happen if new views are added or an existing view expands and pushes 701 // another view out of bounds. 702 if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout() 703 || !supportsPredictiveItemAnimations()) { 704 return; 705 } 706 // to make the logic simpler, we calculate the size of children and call fill. 707 int scrapExtraStart = 0, scrapExtraEnd = 0; 708 final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList(); 709 final int scrapSize = scrapList.size(); 710 final int firstChildPos = getPosition(getChildAt(0)); 711 for (int i = 0; i < scrapSize; i++) { 712 RecyclerView.ViewHolder scrap = scrapList.get(i); 713 if (scrap.isRemoved()) { 714 continue; 715 } 716 final int position = scrap.getLayoutPosition(); 717 final int direction = position < firstChildPos != mShouldReverseLayout 718 ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; 719 if (direction == LayoutState.LAYOUT_START) { 720 scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); 721 } else { 722 scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); 723 } 724 } 725 726 if (DEBUG) { 727 Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart 728 + " towards start and " + scrapExtraEnd + " towards end"); 729 } 730 mLayoutState.mScrapList = scrapList; 731 if (scrapExtraStart > 0) { 732 View anchor = getChildClosestToStart(); 733 updateLayoutStateToFillStart(getPosition(anchor), startOffset); 734 mLayoutState.mExtra = scrapExtraStart; 735 mLayoutState.mAvailable = 0; 736 mLayoutState.assignPositionFromScrapList(); 737 fill(recycler, mLayoutState, state, false); 738 } 739 740 if (scrapExtraEnd > 0) { 741 View anchor = getChildClosestToEnd(); 742 updateLayoutStateToFillEnd(getPosition(anchor), endOffset); 743 mLayoutState.mExtra = scrapExtraEnd; 744 mLayoutState.mAvailable = 0; 745 mLayoutState.assignPositionFromScrapList(); 746 fill(recycler, mLayoutState, state, false); 747 } 748 mLayoutState.mScrapList = null; 749 } 750 751 private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, 752 AnchorInfo anchorInfo) { 753 if (updateAnchorFromPendingData(state, anchorInfo)) { 754 if (DEBUG) { 755 Log.d(TAG, "updated anchor info from pending information"); 756 } 757 return; 758 } 759 760 if (updateAnchorFromChildren(recycler, state, anchorInfo)) { 761 if (DEBUG) { 762 Log.d(TAG, "updated anchor info from existing children"); 763 } 764 return; 765 } 766 if (DEBUG) { 767 Log.d(TAG, "deciding anchor info for fresh state"); 768 } 769 anchorInfo.assignCoordinateFromPadding(); 770 anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; 771 } 772 773 /** 774 * Finds an anchor child from existing Views. Most of the time, this is the view closest to 775 * start or end that has a valid position (e.g. not removed). 776 * <p> 777 * If a child has focus, it is given priority. 778 */ 779 private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler, 780 RecyclerView.State state, AnchorInfo anchorInfo) { 781 if (getChildCount() == 0) { 782 return false; 783 } 784 final View focused = getFocusedChild(); 785 if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) { 786 anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); 787 return true; 788 } 789 if (mLastStackFromEnd != mStackFromEnd) { 790 return false; 791 } 792 View referenceChild = anchorInfo.mLayoutFromEnd 793 ? findReferenceChildClosestToEnd(recycler, state) 794 : findReferenceChildClosestToStart(recycler, state); 795 if (referenceChild != null) { 796 anchorInfo.assignFromView(referenceChild, getPosition(referenceChild)); 797 // If all visible views are removed in 1 pass, reference child might be out of bounds. 798 // If that is the case, offset it back to 0 so that we use these pre-layout children. 799 if (!state.isPreLayout() && supportsPredictiveItemAnimations()) { 800 // validate this child is at least partially visible. if not, offset it to start 801 final boolean notVisible = 802 mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper 803 .getEndAfterPadding() 804 || mOrientationHelper.getDecoratedEnd(referenceChild) 805 < mOrientationHelper.getStartAfterPadding(); 806 if (notVisible) { 807 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd 808 ? mOrientationHelper.getEndAfterPadding() 809 : mOrientationHelper.getStartAfterPadding(); 810 } 811 } 812 return true; 813 } 814 return false; 815 } 816 817 /** 818 * If there is a pending scroll position or saved states, updates the anchor info from that 819 * data and returns true 820 */ 821 private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { 822 if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) { 823 return false; 824 } 825 // validate scroll position 826 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { 827 mPendingScrollPosition = RecyclerView.NO_POSITION; 828 mPendingScrollPositionOffset = INVALID_OFFSET; 829 if (DEBUG) { 830 Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition); 831 } 832 return false; 833 } 834 835 // if child is visible, try to make it a reference child and ensure it is fully visible. 836 // if child is not visible, align it depending on its virtual position. 837 anchorInfo.mPosition = mPendingScrollPosition; 838 if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { 839 // Anchor offset depends on how that child was laid out. Here, we update it 840 // according to our current view bounds 841 anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; 842 if (anchorInfo.mLayoutFromEnd) { 843 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() 844 - mPendingSavedState.mAnchorOffset; 845 } else { 846 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() 847 + mPendingSavedState.mAnchorOffset; 848 } 849 return true; 850 } 851 852 if (mPendingScrollPositionOffset == INVALID_OFFSET) { 853 View child = findViewByPosition(mPendingScrollPosition); 854 if (child != null) { 855 final int childSize = mOrientationHelper.getDecoratedMeasurement(child); 856 if (childSize > mOrientationHelper.getTotalSpace()) { 857 // item does not fit. fix depending on layout direction 858 anchorInfo.assignCoordinateFromPadding(); 859 return true; 860 } 861 final int startGap = mOrientationHelper.getDecoratedStart(child) 862 - mOrientationHelper.getStartAfterPadding(); 863 if (startGap < 0) { 864 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding(); 865 anchorInfo.mLayoutFromEnd = false; 866 return true; 867 } 868 final int endGap = mOrientationHelper.getEndAfterPadding() 869 - mOrientationHelper.getDecoratedEnd(child); 870 if (endGap < 0) { 871 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding(); 872 anchorInfo.mLayoutFromEnd = true; 873 return true; 874 } 875 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd 876 ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper 877 .getTotalSpaceChange()) 878 : mOrientationHelper.getDecoratedStart(child); 879 } else { // item is not visible. 880 if (getChildCount() > 0) { 881 // get position of any child, does not matter 882 int pos = getPosition(getChildAt(0)); 883 anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos 884 == mShouldReverseLayout; 885 } 886 anchorInfo.assignCoordinateFromPadding(); 887 } 888 return true; 889 } 890 // override layout from end values for consistency 891 anchorInfo.mLayoutFromEnd = mShouldReverseLayout; 892 // if this changes, we should update prepareForDrop as well 893 if (mShouldReverseLayout) { 894 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() 895 - mPendingScrollPositionOffset; 896 } else { 897 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() 898 + mPendingScrollPositionOffset; 899 } 900 return true; 901 } 902 903 /** 904 * @return The final offset amount for children 905 */ 906 private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler, 907 RecyclerView.State state, boolean canOffsetChildren) { 908 int gap = mOrientationHelper.getEndAfterPadding() - endOffset; 909 int fixOffset = 0; 910 if (gap > 0) { 911 fixOffset = -scrollBy(-gap, recycler, state); 912 } else { 913 return 0; // nothing to fix 914 } 915 // move offset according to scroll amount 916 endOffset += fixOffset; 917 if (canOffsetChildren) { 918 // re-calculate gap, see if we could fix it 919 gap = mOrientationHelper.getEndAfterPadding() - endOffset; 920 if (gap > 0) { 921 mOrientationHelper.offsetChildren(gap); 922 return gap + fixOffset; 923 } 924 } 925 return fixOffset; 926 } 927 928 /** 929 * @return The final offset amount for children 930 */ 931 private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, 932 RecyclerView.State state, boolean canOffsetChildren) { 933 int gap = startOffset - mOrientationHelper.getStartAfterPadding(); 934 int fixOffset = 0; 935 if (gap > 0) { 936 // check if we should fix this gap. 937 fixOffset = -scrollBy(gap, recycler, state); 938 } else { 939 return 0; // nothing to fix 940 } 941 startOffset += fixOffset; 942 if (canOffsetChildren) { 943 // re-calculate gap, see if we could fix it 944 gap = startOffset - mOrientationHelper.getStartAfterPadding(); 945 if (gap > 0) { 946 mOrientationHelper.offsetChildren(-gap); 947 return fixOffset - gap; 948 } 949 } 950 return fixOffset; 951 } 952 953 private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) { 954 updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); 955 } 956 957 private void updateLayoutStateToFillEnd(int itemPosition, int offset) { 958 mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset; 959 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : 960 LayoutState.ITEM_DIRECTION_TAIL; 961 mLayoutState.mCurrentPosition = itemPosition; 962 mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; 963 mLayoutState.mOffset = offset; 964 mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; 965 } 966 967 private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) { 968 updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate); 969 } 970 971 private void updateLayoutStateToFillStart(int itemPosition, int offset) { 972 mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding(); 973 mLayoutState.mCurrentPosition = itemPosition; 974 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : 975 LayoutState.ITEM_DIRECTION_HEAD; 976 mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START; 977 mLayoutState.mOffset = offset; 978 mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; 979 980 } 981 982 protected boolean isLayoutRTL() { 983 return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; 984 } 985 986 void ensureLayoutState() { 987 if (mLayoutState == null) { 988 mLayoutState = createLayoutState(); 989 } 990 } 991 992 /** 993 * Test overrides this to plug some tracking and verification. 994 * 995 * @return A new LayoutState 996 */ 997 LayoutState createLayoutState() { 998 return new LayoutState(); 999 } 1000 1001 /** 1002 * <p>Scroll the RecyclerView to make the position visible.</p> 1003 * 1004 * <p>RecyclerView will scroll the minimum amount that is necessary to make the 1005 * target position visible. If you are looking for a similar behavior to 1006 * {@link android.widget.ListView#setSelection(int)} or 1007 * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use 1008 * {@link #scrollToPositionWithOffset(int, int)}.</p> 1009 * 1010 * <p>Note that scroll position change will not be reflected until the next layout call.</p> 1011 * 1012 * @param position Scroll to this adapter position 1013 * @see #scrollToPositionWithOffset(int, int) 1014 */ 1015 @Override 1016 public void scrollToPosition(int position) { 1017 mPendingScrollPosition = position; 1018 mPendingScrollPositionOffset = INVALID_OFFSET; 1019 if (mPendingSavedState != null) { 1020 mPendingSavedState.invalidateAnchor(); 1021 } 1022 requestLayout(); 1023 } 1024 1025 /** 1026 * Scroll to the specified adapter position with the given offset from resolved layout 1027 * start. Resolved layout start depends on {@link #getReverseLayout()}, 1028 * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}. 1029 * <p> 1030 * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling 1031 * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that 1032 * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom. 1033 * <p> 1034 * Note that scroll position change will not be reflected until the next layout call. 1035 * <p> 1036 * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. 1037 * 1038 * @param position Index (starting at 0) of the reference item. 1039 * @param offset The distance (in pixels) between the start edge of the item view and 1040 * start edge of the RecyclerView. 1041 * @see #setReverseLayout(boolean) 1042 * @see #scrollToPosition(int) 1043 */ 1044 public void scrollToPositionWithOffset(int position, int offset) { 1045 mPendingScrollPosition = position; 1046 mPendingScrollPositionOffset = offset; 1047 if (mPendingSavedState != null) { 1048 mPendingSavedState.invalidateAnchor(); 1049 } 1050 requestLayout(); 1051 } 1052 1053 1054 /** 1055 * {@inheritDoc} 1056 */ 1057 @Override 1058 public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, 1059 RecyclerView.State state) { 1060 if (mOrientation == VERTICAL) { 1061 return 0; 1062 } 1063 return scrollBy(dx, recycler, state); 1064 } 1065 1066 /** 1067 * {@inheritDoc} 1068 */ 1069 @Override 1070 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, 1071 RecyclerView.State state) { 1072 if (mOrientation == HORIZONTAL) { 1073 return 0; 1074 } 1075 return scrollBy(dy, recycler, state); 1076 } 1077 1078 @Override 1079 public int computeHorizontalScrollOffset(RecyclerView.State state) { 1080 return computeScrollOffset(state); 1081 } 1082 1083 @Override 1084 public int computeVerticalScrollOffset(RecyclerView.State state) { 1085 return computeScrollOffset(state); 1086 } 1087 1088 @Override 1089 public int computeHorizontalScrollExtent(RecyclerView.State state) { 1090 return computeScrollExtent(state); 1091 } 1092 1093 @Override 1094 public int computeVerticalScrollExtent(RecyclerView.State state) { 1095 return computeScrollExtent(state); 1096 } 1097 1098 @Override 1099 public int computeHorizontalScrollRange(RecyclerView.State state) { 1100 return computeScrollRange(state); 1101 } 1102 1103 @Override 1104 public int computeVerticalScrollRange(RecyclerView.State state) { 1105 return computeScrollRange(state); 1106 } 1107 1108 private int computeScrollOffset(RecyclerView.State state) { 1109 if (getChildCount() == 0) { 1110 return 0; 1111 } 1112 ensureLayoutState(); 1113 return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper, 1114 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), 1115 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), 1116 this, mSmoothScrollbarEnabled, mShouldReverseLayout); 1117 } 1118 1119 private int computeScrollExtent(RecyclerView.State state) { 1120 if (getChildCount() == 0) { 1121 return 0; 1122 } 1123 ensureLayoutState(); 1124 return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper, 1125 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), 1126 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), 1127 this, mSmoothScrollbarEnabled); 1128 } 1129 1130 private int computeScrollRange(RecyclerView.State state) { 1131 if (getChildCount() == 0) { 1132 return 0; 1133 } 1134 ensureLayoutState(); 1135 return ScrollbarHelper.computeScrollRange(state, mOrientationHelper, 1136 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), 1137 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), 1138 this, mSmoothScrollbarEnabled); 1139 } 1140 1141 /** 1142 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed 1143 * based on the number of visible pixels in the visible items. This however assumes that all 1144 * list items have similar or equal widths or heights (depending on list orientation). 1145 * If you use a list in which items have different dimensions, the scrollbar will change 1146 * appearance as the user scrolls through the list. To avoid this issue, you need to disable 1147 * this property. 1148 * 1149 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based 1150 * solely on the number of items in the adapter and the position of the visible items inside 1151 * the adapter. This provides a stable scrollbar as the user navigates through a list of items 1152 * with varying widths / heights. 1153 * 1154 * @param enabled Whether or not to enable smooth scrollbar. 1155 * 1156 * @see #setSmoothScrollbarEnabled(boolean) 1157 */ 1158 public void setSmoothScrollbarEnabled(boolean enabled) { 1159 mSmoothScrollbarEnabled = enabled; 1160 } 1161 1162 /** 1163 * Returns the current state of the smooth scrollbar feature. It is enabled by default. 1164 * 1165 * @return True if smooth scrollbar is enabled, false otherwise. 1166 * 1167 * @see #setSmoothScrollbarEnabled(boolean) 1168 */ 1169 public boolean isSmoothScrollbarEnabled() { 1170 return mSmoothScrollbarEnabled; 1171 } 1172 1173 private void updateLayoutState(int layoutDirection, int requiredSpace, 1174 boolean canUseExistingSpace, RecyclerView.State state) { 1175 // If parent provides a hint, don't measure unlimited. 1176 mLayoutState.mInfinite = resolveIsInfinite(); 1177 mLayoutState.mExtra = getExtraLayoutSpace(state); 1178 mLayoutState.mLayoutDirection = layoutDirection; 1179 int scrollingOffset; 1180 if (layoutDirection == LayoutState.LAYOUT_END) { 1181 mLayoutState.mExtra += mOrientationHelper.getEndPadding(); 1182 // get the first child in the direction we are going 1183 final View child = getChildClosestToEnd(); 1184 // the direction in which we are traversing children 1185 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD 1186 : LayoutState.ITEM_DIRECTION_TAIL; 1187 mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; 1188 mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child); 1189 // calculate how much we can scroll without adding new children (independent of layout) 1190 scrollingOffset = mOrientationHelper.getDecoratedEnd(child) 1191 - mOrientationHelper.getEndAfterPadding(); 1192 1193 } else { 1194 final View child = getChildClosestToStart(); 1195 mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding(); 1196 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL 1197 : LayoutState.ITEM_DIRECTION_HEAD; 1198 mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; 1199 mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); 1200 scrollingOffset = -mOrientationHelper.getDecoratedStart(child) 1201 + mOrientationHelper.getStartAfterPadding(); 1202 } 1203 mLayoutState.mAvailable = requiredSpace; 1204 if (canUseExistingSpace) { 1205 mLayoutState.mAvailable -= scrollingOffset; 1206 } 1207 mLayoutState.mScrollingOffset = scrollingOffset; 1208 } 1209 1210 boolean resolveIsInfinite() { 1211 return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED 1212 && mOrientationHelper.getEnd() == 0; 1213 } 1214 1215 void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, 1216 LayoutPrefetchRegistry layoutPrefetchRegistry) { 1217 final int pos = layoutState.mCurrentPosition; 1218 if (pos >= 0 && pos < state.getItemCount()) { 1219 layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset)); 1220 } 1221 } 1222 1223 @Override 1224 public void collectInitialPrefetchPositions(int adapterItemCount, 1225 LayoutPrefetchRegistry layoutPrefetchRegistry) { 1226 final boolean fromEnd; 1227 final int anchorPos; 1228 if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { 1229 // use restored state, since it hasn't been resolved yet 1230 fromEnd = mPendingSavedState.mAnchorLayoutFromEnd; 1231 anchorPos = mPendingSavedState.mAnchorPosition; 1232 } else { 1233 resolveShouldLayoutReverse(); 1234 fromEnd = mShouldReverseLayout; 1235 if (mPendingScrollPosition == RecyclerView.NO_POSITION) { 1236 anchorPos = fromEnd ? adapterItemCount - 1 : 0; 1237 } else { 1238 anchorPos = mPendingScrollPosition; 1239 } 1240 } 1241 1242 final int direction = fromEnd 1243 ? LayoutState.ITEM_DIRECTION_HEAD 1244 : LayoutState.ITEM_DIRECTION_TAIL; 1245 int targetPos = anchorPos; 1246 for (int i = 0; i < mInitialPrefetchItemCount; i++) { 1247 if (targetPos >= 0 && targetPos < adapterItemCount) { 1248 layoutPrefetchRegistry.addPosition(targetPos, 0); 1249 } else { 1250 break; // no more to prefetch 1251 } 1252 targetPos += direction; 1253 } 1254 } 1255 1256 /** 1257 * Sets the number of items to prefetch in 1258 * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines 1259 * how many inner items should be prefetched when this LayoutManager's RecyclerView 1260 * is nested inside another RecyclerView. 1261 * 1262 * <p>Set this value to the number of items this inner LayoutManager will display when it is 1263 * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items 1264 * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.</p> 1265 * 1266 * <p>For example, take a vertically scrolling RecyclerView with horizontally scrolling inner 1267 * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing 1268 * <code>4</code> to this method for each inner RecyclerView's LinearLayoutManager will enable 1269 * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early, 1270 * before it is scrolled on screen, instead of just the default 2.</p> 1271 * 1272 * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView 1273 * nested in another RecyclerView.</p> 1274 * 1275 * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of 1276 * views that will be visible in this view can incur unnecessary bind work, and an increase to 1277 * the number of Views created and in active use.</p> 1278 * 1279 * @param itemCount Number of items to prefetch 1280 * 1281 * @see #isItemPrefetchEnabled() 1282 * @see #getInitialPrefetchItemCount() 1283 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 1284 */ 1285 public void setInitialPrefetchItemCount(int itemCount) { 1286 mInitialPrefetchItemCount = itemCount; 1287 } 1288 1289 /** 1290 * Gets the number of items to prefetch in 1291 * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines 1292 * how many inner items should be prefetched when this LayoutManager's RecyclerView 1293 * is nested inside another RecyclerView. 1294 * 1295 * @see #isItemPrefetchEnabled() 1296 * @see #setInitialPrefetchItemCount(int) 1297 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 1298 * 1299 * @return number of items to prefetch. 1300 */ 1301 public int getInitialPrefetchItemCount() { 1302 return mInitialPrefetchItemCount; 1303 } 1304 1305 @Override 1306 public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, 1307 LayoutPrefetchRegistry layoutPrefetchRegistry) { 1308 int delta = (mOrientation == HORIZONTAL) ? dx : dy; 1309 if (getChildCount() == 0 || delta == 0) { 1310 // can't support this scroll, so don't bother prefetching 1311 return; 1312 } 1313 1314 ensureLayoutState(); 1315 final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; 1316 final int absDy = Math.abs(delta); 1317 updateLayoutState(layoutDirection, absDy, true, state); 1318 collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry); 1319 } 1320 1321 int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { 1322 if (getChildCount() == 0 || dy == 0) { 1323 return 0; 1324 } 1325 mLayoutState.mRecycle = true; 1326 ensureLayoutState(); 1327 final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; 1328 final int absDy = Math.abs(dy); 1329 updateLayoutState(layoutDirection, absDy, true, state); 1330 final int consumed = mLayoutState.mScrollingOffset 1331 + fill(recycler, mLayoutState, state, false); 1332 if (consumed < 0) { 1333 if (DEBUG) { 1334 Log.d(TAG, "Don't have any more elements to scroll"); 1335 } 1336 return 0; 1337 } 1338 final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; 1339 mOrientationHelper.offsetChildren(-scrolled); 1340 if (DEBUG) { 1341 Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled); 1342 } 1343 mLayoutState.mLastScrollDelta = scrolled; 1344 return scrolled; 1345 } 1346 1347 @Override 1348 public void assertNotInLayoutOrScroll(String message) { 1349 if (mPendingSavedState == null) { 1350 super.assertNotInLayoutOrScroll(message); 1351 } 1352 } 1353 1354 /** 1355 * Recycles children between given indices. 1356 * 1357 * @param startIndex inclusive 1358 * @param endIndex exclusive 1359 */ 1360 private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { 1361 if (startIndex == endIndex) { 1362 return; 1363 } 1364 if (DEBUG) { 1365 Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items"); 1366 } 1367 if (endIndex > startIndex) { 1368 for (int i = endIndex - 1; i >= startIndex; i--) { 1369 removeAndRecycleViewAt(i, recycler); 1370 } 1371 } else { 1372 for (int i = startIndex; i > endIndex; i--) { 1373 removeAndRecycleViewAt(i, recycler); 1374 } 1375 } 1376 } 1377 1378 /** 1379 * Recycles views that went out of bounds after scrolling towards the end of the layout. 1380 * <p> 1381 * Checks both layout position and visible position to guarantee that the view is not visible. 1382 * 1383 * @param recycler Recycler instance of {@link RecyclerView} 1384 * @param dt This can be used to add additional padding to the visible area. This is used 1385 * to detect children that will go out of bounds after scrolling, without 1386 * actually moving them. 1387 */ 1388 private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { 1389 if (dt < 0) { 1390 if (DEBUG) { 1391 Log.d(TAG, "Called recycle from start with a negative value. This might happen" 1392 + " during layout changes but may be sign of a bug"); 1393 } 1394 return; 1395 } 1396 // ignore padding, ViewGroup may not clip children. 1397 final int limit = dt; 1398 final int childCount = getChildCount(); 1399 if (mShouldReverseLayout) { 1400 for (int i = childCount - 1; i >= 0; i--) { 1401 View child = getChildAt(i); 1402 if (mOrientationHelper.getDecoratedEnd(child) > limit 1403 || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { 1404 // stop here 1405 recycleChildren(recycler, childCount - 1, i); 1406 return; 1407 } 1408 } 1409 } else { 1410 for (int i = 0; i < childCount; i++) { 1411 View child = getChildAt(i); 1412 if (mOrientationHelper.getDecoratedEnd(child) > limit 1413 || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { 1414 // stop here 1415 recycleChildren(recycler, 0, i); 1416 return; 1417 } 1418 } 1419 } 1420 } 1421 1422 1423 /** 1424 * Recycles views that went out of bounds after scrolling towards the start of the layout. 1425 * <p> 1426 * Checks both layout position and visible position to guarantee that the view is not visible. 1427 * 1428 * @param recycler Recycler instance of {@link RecyclerView} 1429 * @param dt This can be used to add additional padding to the visible area. This is used 1430 * to detect children that will go out of bounds after scrolling, without 1431 * actually moving them. 1432 */ 1433 private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { 1434 final int childCount = getChildCount(); 1435 if (dt < 0) { 1436 if (DEBUG) { 1437 Log.d(TAG, "Called recycle from end with a negative value. This might happen" 1438 + " during layout changes but may be sign of a bug"); 1439 } 1440 return; 1441 } 1442 final int limit = mOrientationHelper.getEnd() - dt; 1443 if (mShouldReverseLayout) { 1444 for (int i = 0; i < childCount; i++) { 1445 View child = getChildAt(i); 1446 if (mOrientationHelper.getDecoratedStart(child) < limit 1447 || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { 1448 // stop here 1449 recycleChildren(recycler, 0, i); 1450 return; 1451 } 1452 } 1453 } else { 1454 for (int i = childCount - 1; i >= 0; i--) { 1455 View child = getChildAt(i); 1456 if (mOrientationHelper.getDecoratedStart(child) < limit 1457 || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { 1458 // stop here 1459 recycleChildren(recycler, childCount - 1, i); 1460 return; 1461 } 1462 } 1463 } 1464 } 1465 1466 /** 1467 * Helper method to call appropriate recycle method depending on current layout direction 1468 * 1469 * @param recycler Current recycler that is attached to RecyclerView 1470 * @param layoutState Current layout state. Right now, this object does not change but 1471 * we may consider moving it out of this view so passing around as a 1472 * parameter for now, rather than accessing {@link #mLayoutState} 1473 * @see #recycleViewsFromStart(RecyclerView.Recycler, int) 1474 * @see #recycleViewsFromEnd(RecyclerView.Recycler, int) 1475 * @see LinearLayoutManager.LayoutState#mLayoutDirection 1476 */ 1477 private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { 1478 if (!layoutState.mRecycle || layoutState.mInfinite) { 1479 return; 1480 } 1481 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1482 recycleViewsFromEnd(recycler, layoutState.mScrollingOffset); 1483 } else { 1484 recycleViewsFromStart(recycler, layoutState.mScrollingOffset); 1485 } 1486 } 1487 1488 /** 1489 * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly 1490 * independent from the rest of the {@link LinearLayoutManager} 1491 * and with little change, can be made publicly available as a helper class. 1492 * 1493 * @param recycler Current recycler that is attached to RecyclerView 1494 * @param layoutState Configuration on how we should fill out the available space. 1495 * @param state Context passed by the RecyclerView to control scroll steps. 1496 * @param stopOnFocusable If true, filling stops in the first focusable new child 1497 * @return Number of pixels that it added. Useful for scroll functions. 1498 */ 1499 int fill(RecyclerView.Recycler recycler, LayoutState layoutState, 1500 RecyclerView.State state, boolean stopOnFocusable) { 1501 // max offset we should set is mFastScroll + available 1502 final int start = layoutState.mAvailable; 1503 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { 1504 // TODO ugly bug fix. should not happen 1505 if (layoutState.mAvailable < 0) { 1506 layoutState.mScrollingOffset += layoutState.mAvailable; 1507 } 1508 recycleByLayoutState(recycler, layoutState); 1509 } 1510 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; 1511 LayoutChunkResult layoutChunkResult = mLayoutChunkResult; 1512 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { 1513 layoutChunkResult.resetInternal(); 1514 if (RecyclerView.VERBOSE_TRACING) { 1515 TraceCompat.beginSection("LLM LayoutChunk"); 1516 } 1517 layoutChunk(recycler, state, layoutState, layoutChunkResult); 1518 if (RecyclerView.VERBOSE_TRACING) { 1519 TraceCompat.endSection(); 1520 } 1521 if (layoutChunkResult.mFinished) { 1522 break; 1523 } 1524 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; 1525 /** 1526 * Consume the available space if: 1527 * * layoutChunk did not request to be ignored 1528 * * OR we are laying out scrap children 1529 * * OR we are not doing pre-layout 1530 */ 1531 if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null 1532 || !state.isPreLayout()) { 1533 layoutState.mAvailable -= layoutChunkResult.mConsumed; 1534 // we keep a separate remaining space because mAvailable is important for recycling 1535 remainingSpace -= layoutChunkResult.mConsumed; 1536 } 1537 1538 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { 1539 layoutState.mScrollingOffset += layoutChunkResult.mConsumed; 1540 if (layoutState.mAvailable < 0) { 1541 layoutState.mScrollingOffset += layoutState.mAvailable; 1542 } 1543 recycleByLayoutState(recycler, layoutState); 1544 } 1545 if (stopOnFocusable && layoutChunkResult.mFocusable) { 1546 break; 1547 } 1548 } 1549 if (DEBUG) { 1550 validateChildOrder(); 1551 } 1552 return start - layoutState.mAvailable; 1553 } 1554 1555 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, 1556 LayoutState layoutState, LayoutChunkResult result) { 1557 View view = layoutState.next(recycler); 1558 if (view == null) { 1559 if (DEBUG && layoutState.mScrapList == null) { 1560 throw new RuntimeException("received null view when unexpected"); 1561 } 1562 // if we are laying out views in scrap, this may return null which means there is 1563 // no more items to layout. 1564 result.mFinished = true; 1565 return; 1566 } 1567 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 1568 if (layoutState.mScrapList == null) { 1569 if (mShouldReverseLayout == (layoutState.mLayoutDirection 1570 == LayoutState.LAYOUT_START)) { 1571 addView(view); 1572 } else { 1573 addView(view, 0); 1574 } 1575 } else { 1576 if (mShouldReverseLayout == (layoutState.mLayoutDirection 1577 == LayoutState.LAYOUT_START)) { 1578 addDisappearingView(view); 1579 } else { 1580 addDisappearingView(view, 0); 1581 } 1582 } 1583 measureChildWithMargins(view, 0, 0); 1584 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); 1585 int left, top, right, bottom; 1586 if (mOrientation == VERTICAL) { 1587 if (isLayoutRTL()) { 1588 right = getWidth() - getPaddingRight(); 1589 left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); 1590 } else { 1591 left = getPaddingLeft(); 1592 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); 1593 } 1594 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1595 bottom = layoutState.mOffset; 1596 top = layoutState.mOffset - result.mConsumed; 1597 } else { 1598 top = layoutState.mOffset; 1599 bottom = layoutState.mOffset + result.mConsumed; 1600 } 1601 } else { 1602 top = getPaddingTop(); 1603 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); 1604 1605 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1606 right = layoutState.mOffset; 1607 left = layoutState.mOffset - result.mConsumed; 1608 } else { 1609 left = layoutState.mOffset; 1610 right = layoutState.mOffset + result.mConsumed; 1611 } 1612 } 1613 // We calculate everything with View's bounding box (which includes decor and margins) 1614 // To calculate correct layout position, we subtract margins. 1615 layoutDecoratedWithMargins(view, left, top, right, bottom); 1616 if (DEBUG) { 1617 Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" 1618 + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" 1619 + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); 1620 } 1621 // Consume the available space if the view is not removed OR changed 1622 if (params.isItemRemoved() || params.isItemChanged()) { 1623 result.mIgnoreConsumed = true; 1624 } 1625 result.mFocusable = view.hasFocusable(); 1626 } 1627 1628 @Override 1629 boolean shouldMeasureTwice() { 1630 return getHeightMode() != View.MeasureSpec.EXACTLY 1631 && getWidthMode() != View.MeasureSpec.EXACTLY 1632 && hasFlexibleChildInBothOrientations(); 1633 } 1634 1635 /** 1636 * Converts a focusDirection to orientation. 1637 * 1638 * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 1639 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 1640 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 1641 * or 0 for not applicable 1642 * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction 1643 * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. 1644 */ 1645 int convertFocusDirectionToLayoutDirection(int focusDirection) { 1646 switch (focusDirection) { 1647 case View.FOCUS_BACKWARD: 1648 if (mOrientation == VERTICAL) { 1649 return LayoutState.LAYOUT_START; 1650 } else if (isLayoutRTL()) { 1651 return LayoutState.LAYOUT_END; 1652 } else { 1653 return LayoutState.LAYOUT_START; 1654 } 1655 case View.FOCUS_FORWARD: 1656 if (mOrientation == VERTICAL) { 1657 return LayoutState.LAYOUT_END; 1658 } else if (isLayoutRTL()) { 1659 return LayoutState.LAYOUT_START; 1660 } else { 1661 return LayoutState.LAYOUT_END; 1662 } 1663 case View.FOCUS_UP: 1664 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START 1665 : LayoutState.INVALID_LAYOUT; 1666 case View.FOCUS_DOWN: 1667 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END 1668 : LayoutState.INVALID_LAYOUT; 1669 case View.FOCUS_LEFT: 1670 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START 1671 : LayoutState.INVALID_LAYOUT; 1672 case View.FOCUS_RIGHT: 1673 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END 1674 : LayoutState.INVALID_LAYOUT; 1675 default: 1676 if (DEBUG) { 1677 Log.d(TAG, "Unknown focus request:" + focusDirection); 1678 } 1679 return LayoutState.INVALID_LAYOUT; 1680 } 1681 1682 } 1683 1684 /** 1685 * Convenience method to find the child closes to start. Caller should check it has enough 1686 * children. 1687 * 1688 * @return The child closes to start of the layout from user's perspective. 1689 */ 1690 private View getChildClosestToStart() { 1691 return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0); 1692 } 1693 1694 /** 1695 * Convenience method to find the child closes to end. Caller should check it has enough 1696 * children. 1697 * 1698 * @return The child closes to end of the layout from user's perspective. 1699 */ 1700 private View getChildClosestToEnd() { 1701 return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1); 1702 } 1703 1704 /** 1705 * Convenience method to find the visible child closes to start. Caller should check if it has 1706 * enough children. 1707 * 1708 * @param completelyVisible Whether child should be completely visible or not 1709 * @return The first visible child closest to start of the layout from user's perspective. 1710 */ 1711 private View findFirstVisibleChildClosestToStart(boolean completelyVisible, 1712 boolean acceptPartiallyVisible) { 1713 if (mShouldReverseLayout) { 1714 return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible, 1715 acceptPartiallyVisible); 1716 } else { 1717 return findOneVisibleChild(0, getChildCount(), completelyVisible, 1718 acceptPartiallyVisible); 1719 } 1720 } 1721 1722 /** 1723 * Convenience method to find the visible child closes to end. Caller should check if it has 1724 * enough children. 1725 * 1726 * @param completelyVisible Whether child should be completely visible or not 1727 * @return The first visible child closest to end of the layout from user's perspective. 1728 */ 1729 private View findFirstVisibleChildClosestToEnd(boolean completelyVisible, 1730 boolean acceptPartiallyVisible) { 1731 if (mShouldReverseLayout) { 1732 return findOneVisibleChild(0, getChildCount(), completelyVisible, 1733 acceptPartiallyVisible); 1734 } else { 1735 return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible, 1736 acceptPartiallyVisible); 1737 } 1738 } 1739 1740 1741 /** 1742 * Among the children that are suitable to be considered as an anchor child, returns the one 1743 * closest to the end of the layout. 1744 * <p> 1745 * Due to ambiguous adapter updates or children being removed, some children's positions may be 1746 * invalid. This method is a best effort to find a position within adapter bounds if possible. 1747 * <p> 1748 * It also prioritizes children that are within the visible bounds. 1749 * @return A View that can be used an an anchor View. 1750 */ 1751 private View findReferenceChildClosestToEnd(RecyclerView.Recycler recycler, 1752 RecyclerView.State state) { 1753 return mShouldReverseLayout ? findFirstReferenceChild(recycler, state) : 1754 findLastReferenceChild(recycler, state); 1755 } 1756 1757 /** 1758 * Among the children that are suitable to be considered as an anchor child, returns the one 1759 * closest to the start of the layout. 1760 * <p> 1761 * Due to ambiguous adapter updates or children being removed, some children's positions may be 1762 * invalid. This method is a best effort to find a position within adapter bounds if possible. 1763 * <p> 1764 * It also prioritizes children that are within the visible bounds. 1765 * 1766 * @return A View that can be used an an anchor View. 1767 */ 1768 private View findReferenceChildClosestToStart(RecyclerView.Recycler recycler, 1769 RecyclerView.State state) { 1770 return mShouldReverseLayout ? findLastReferenceChild(recycler, state) : 1771 findFirstReferenceChild(recycler, state); 1772 } 1773 1774 private View findFirstReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) { 1775 return findReferenceChild(recycler, state, 0, getChildCount(), state.getItemCount()); 1776 } 1777 1778 private View findLastReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) { 1779 return findReferenceChild(recycler, state, getChildCount() - 1, -1, state.getItemCount()); 1780 } 1781 1782 // overridden by GridLayoutManager 1783 View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, 1784 int start, int end, int itemCount) { 1785 ensureLayoutState(); 1786 View invalidMatch = null; 1787 View outOfBoundsMatch = null; 1788 final int boundsStart = mOrientationHelper.getStartAfterPadding(); 1789 final int boundsEnd = mOrientationHelper.getEndAfterPadding(); 1790 final int diff = end > start ? 1 : -1; 1791 for (int i = start; i != end; i += diff) { 1792 final View view = getChildAt(i); 1793 final int position = getPosition(view); 1794 if (position >= 0 && position < itemCount) { 1795 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) { 1796 if (invalidMatch == null) { 1797 invalidMatch = view; // removed item, least preferred 1798 } 1799 } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd 1800 || mOrientationHelper.getDecoratedEnd(view) < boundsStart) { 1801 if (outOfBoundsMatch == null) { 1802 outOfBoundsMatch = view; // item is not visible, less preferred 1803 } 1804 } else { 1805 return view; 1806 } 1807 } 1808 } 1809 return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch; 1810 } 1811 1812 // returns the out-of-bound child view closest to RV's end bounds. An out-of-bound child is 1813 // defined as a child that's either partially or fully invisible (outside RV's padding area). 1814 private View findPartiallyOrCompletelyInvisibleChildClosestToEnd(RecyclerView.Recycler recycler, 1815 RecyclerView.State state) { 1816 return mShouldReverseLayout ? findFirstPartiallyOrCompletelyInvisibleChild(recycler, state) 1817 : findLastPartiallyOrCompletelyInvisibleChild(recycler, state); 1818 } 1819 1820 // returns the out-of-bound child view closest to RV's starting bounds. An out-of-bound child is 1821 // defined as a child that's either partially or fully invisible (outside RV's padding area). 1822 private View findPartiallyOrCompletelyInvisibleChildClosestToStart( 1823 RecyclerView.Recycler recycler, RecyclerView.State state) { 1824 return mShouldReverseLayout ? findLastPartiallyOrCompletelyInvisibleChild(recycler, state) : 1825 findFirstPartiallyOrCompletelyInvisibleChild(recycler, state); 1826 } 1827 1828 private View findFirstPartiallyOrCompletelyInvisibleChild(RecyclerView.Recycler recycler, 1829 RecyclerView.State state) { 1830 return findOnePartiallyOrCompletelyInvisibleChild(0, getChildCount()); 1831 } 1832 1833 private View findLastPartiallyOrCompletelyInvisibleChild(RecyclerView.Recycler recycler, 1834 RecyclerView.State state) { 1835 return findOnePartiallyOrCompletelyInvisibleChild(getChildCount() - 1, -1); 1836 } 1837 1838 /** 1839 * Returns the adapter position of the first visible view. This position does not include 1840 * adapter changes that were dispatched after the last layout pass. 1841 * <p> 1842 * Note that, this value is not affected by layout orientation or item order traversal. 1843 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, 1844 * not in the layout. 1845 * <p> 1846 * If RecyclerView has item decorators, they will be considered in calculations as well. 1847 * <p> 1848 * LayoutManager may pre-cache some views that are not necessarily visible. Those views 1849 * are ignored in this method. 1850 * 1851 * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if 1852 * there aren't any visible items. 1853 * @see #findFirstCompletelyVisibleItemPosition() 1854 * @see #findLastVisibleItemPosition() 1855 */ 1856 public int findFirstVisibleItemPosition() { 1857 final View child = findOneVisibleChild(0, getChildCount(), false, true); 1858 return child == null ? RecyclerView.NO_POSITION : getPosition(child); 1859 } 1860 1861 /** 1862 * Returns the adapter position of the first fully visible view. This position does not include 1863 * adapter changes that were dispatched after the last layout pass. 1864 * <p> 1865 * Note that bounds check is only performed in the current orientation. That means, if 1866 * LayoutManager is horizontal, it will only check the view's left and right edges. 1867 * 1868 * @return The adapter position of the first fully visible item or 1869 * {@link RecyclerView#NO_POSITION} if there aren't any visible items. 1870 * @see #findFirstVisibleItemPosition() 1871 * @see #findLastCompletelyVisibleItemPosition() 1872 */ 1873 public int findFirstCompletelyVisibleItemPosition() { 1874 final View child = findOneVisibleChild(0, getChildCount(), true, false); 1875 return child == null ? RecyclerView.NO_POSITION : getPosition(child); 1876 } 1877 1878 /** 1879 * Returns the adapter position of the last visible view. This position does not include 1880 * adapter changes that were dispatched after the last layout pass. 1881 * <p> 1882 * Note that, this value is not affected by layout orientation or item order traversal. 1883 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, 1884 * not in the layout. 1885 * <p> 1886 * If RecyclerView has item decorators, they will be considered in calculations as well. 1887 * <p> 1888 * LayoutManager may pre-cache some views that are not necessarily visible. Those views 1889 * are ignored in this method. 1890 * 1891 * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if 1892 * there aren't any visible items. 1893 * @see #findLastCompletelyVisibleItemPosition() 1894 * @see #findFirstVisibleItemPosition() 1895 */ 1896 public int findLastVisibleItemPosition() { 1897 final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true); 1898 return child == null ? RecyclerView.NO_POSITION : getPosition(child); 1899 } 1900 1901 /** 1902 * Returns the adapter position of the last fully visible view. This position does not include 1903 * adapter changes that were dispatched after the last layout pass. 1904 * <p> 1905 * Note that bounds check is only performed in the current orientation. That means, if 1906 * LayoutManager is horizontal, it will only check the view's left and right edges. 1907 * 1908 * @return The adapter position of the last fully visible view or 1909 * {@link RecyclerView#NO_POSITION} if there aren't any visible items. 1910 * @see #findLastVisibleItemPosition() 1911 * @see #findFirstCompletelyVisibleItemPosition() 1912 */ 1913 public int findLastCompletelyVisibleItemPosition() { 1914 final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false); 1915 return child == null ? RecyclerView.NO_POSITION : getPosition(child); 1916 } 1917 1918 // Returns the first child that is visible in the provided index range, i.e. either partially or 1919 // fully visible depending on the arguments provided. Completely invisible children are not 1920 // acceptable by this method, but could be returned 1921 // using #findOnePartiallyOrCompletelyInvisibleChild 1922 View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, 1923 boolean acceptPartiallyVisible) { 1924 ensureLayoutState(); 1925 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0; 1926 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; 1927 if (completelyVisible) { 1928 preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS 1929 | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); 1930 } else { 1931 preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 1932 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 1933 } 1934 if (acceptPartiallyVisible) { 1935 acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE 1936 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 1937 } 1938 return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck 1939 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 1940 acceptableBoundsFlag) : mVerticalBoundCheck 1941 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 1942 acceptableBoundsFlag); 1943 } 1944 1945 View findOnePartiallyOrCompletelyInvisibleChild(int fromIndex, int toIndex) { 1946 ensureLayoutState(); 1947 final int next = toIndex > fromIndex ? 1 : (toIndex < fromIndex ? -1 : 0); 1948 if (next == 0) { 1949 return getChildAt(fromIndex); 1950 } 1951 @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0; 1952 @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; 1953 if (mOrientationHelper.getDecoratedStart(getChildAt(fromIndex)) 1954 < mOrientationHelper.getStartAfterPadding()) { 1955 preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE 1956 | ViewBoundsCheck.FLAG_CVE_GT_PVS); 1957 acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS 1958 | ViewBoundsCheck.FLAG_CVE_LT_PVE); 1959 } else { 1960 preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE | ViewBoundsCheck.FLAG_CVS_GT_PVS 1961 | ViewBoundsCheck.FLAG_CVS_LT_PVE); 1962 acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE 1963 | ViewBoundsCheck.FLAG_CVS_GT_PVS); 1964 } 1965 return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck 1966 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 1967 acceptableBoundsFlag) : mVerticalBoundCheck 1968 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 1969 acceptableBoundsFlag); 1970 } 1971 1972 @Override 1973 public View onFocusSearchFailed(View focused, int focusDirection, 1974 RecyclerView.Recycler recycler, RecyclerView.State state) { 1975 resolveShouldLayoutReverse(); 1976 if (getChildCount() == 0) { 1977 return null; 1978 } 1979 1980 final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection); 1981 if (layoutDir == LayoutState.INVALID_LAYOUT) { 1982 return null; 1983 } 1984 ensureLayoutState(); 1985 ensureLayoutState(); 1986 final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); 1987 updateLayoutState(layoutDir, maxScroll, false, state); 1988 mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; 1989 mLayoutState.mRecycle = false; 1990 fill(recycler, mLayoutState, state, true); 1991 1992 // nextCandidate is the first child view in the layout direction that's partially 1993 // within RV's bounds, i.e. part of it is visible or it's completely invisible but still 1994 // touching RV's bounds. This will be the unfocusable candidate view to become visible onto 1995 // the screen if no focusable views are found in the given layout direction. 1996 final View nextCandidate; 1997 if (layoutDir == LayoutState.LAYOUT_START) { 1998 nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToStart(recycler, state); 1999 } else { 2000 nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToEnd(recycler, state); 2001 } 2002 // nextFocus is meaningful only if it refers to a focusable child, in which case it 2003 // indicates the next view to gain focus. 2004 final View nextFocus; 2005 if (layoutDir == LayoutState.LAYOUT_START) { 2006 nextFocus = getChildClosestToStart(); 2007 } else { 2008 nextFocus = getChildClosestToEnd(); 2009 } 2010 if (nextFocus.hasFocusable()) { 2011 if (nextCandidate == null) { 2012 return null; 2013 } 2014 return nextFocus; 2015 } 2016 return nextCandidate; 2017 } 2018 2019 /** 2020 * Used for debugging. 2021 * Logs the internal representation of children to default logger. 2022 */ 2023 private void logChildren() { 2024 Log.d(TAG, "internal representation of views on the screen"); 2025 for (int i = 0; i < getChildCount(); i++) { 2026 View child = getChildAt(i); 2027 Log.d(TAG, "item " + getPosition(child) + ", coord:" 2028 + mOrientationHelper.getDecoratedStart(child)); 2029 } 2030 Log.d(TAG, "=============="); 2031 } 2032 2033 /** 2034 * Used for debugging. 2035 * Validates that child views are laid out in correct order. This is important because rest of 2036 * the algorithm relies on this constraint. 2037 * 2038 * In default layout, child 0 should be closest to screen position 0 and last child should be 2039 * closest to position WIDTH or HEIGHT. 2040 * In reverse layout, last child should be closes to screen position 0 and first child should 2041 * be closest to position WIDTH or HEIGHT 2042 */ 2043 void validateChildOrder() { 2044 Log.d(TAG, "validating child count " + getChildCount()); 2045 if (getChildCount() < 1) { 2046 return; 2047 } 2048 int lastPos = getPosition(getChildAt(0)); 2049 int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0)); 2050 if (mShouldReverseLayout) { 2051 for (int i = 1; i < getChildCount(); i++) { 2052 View child = getChildAt(i); 2053 int pos = getPosition(child); 2054 int screenLoc = mOrientationHelper.getDecoratedStart(child); 2055 if (pos < lastPos) { 2056 logChildren(); 2057 throw new RuntimeException("detected invalid position. loc invalid? " 2058 + (screenLoc < lastScreenLoc)); 2059 } 2060 if (screenLoc > lastScreenLoc) { 2061 logChildren(); 2062 throw new RuntimeException("detected invalid location"); 2063 } 2064 } 2065 } else { 2066 for (int i = 1; i < getChildCount(); i++) { 2067 View child = getChildAt(i); 2068 int pos = getPosition(child); 2069 int screenLoc = mOrientationHelper.getDecoratedStart(child); 2070 if (pos < lastPos) { 2071 logChildren(); 2072 throw new RuntimeException("detected invalid position. loc invalid? " 2073 + (screenLoc < lastScreenLoc)); 2074 } 2075 if (screenLoc < lastScreenLoc) { 2076 logChildren(); 2077 throw new RuntimeException("detected invalid location"); 2078 } 2079 } 2080 } 2081 } 2082 2083 @Override 2084 public boolean supportsPredictiveItemAnimations() { 2085 return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; 2086 } 2087 2088 /** 2089 * @hide This method should be called by ItemTouchHelper only. 2090 */ 2091 @RestrictTo(LIBRARY_GROUP) 2092 @Override 2093 public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) { 2094 assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation"); 2095 ensureLayoutState(); 2096 resolveShouldLayoutReverse(); 2097 final int myPos = getPosition(view); 2098 final int targetPos = getPosition(target); 2099 final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL 2100 : LayoutState.ITEM_DIRECTION_HEAD; 2101 if (mShouldReverseLayout) { 2102 if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) { 2103 scrollToPositionWithOffset(targetPos, 2104 mOrientationHelper.getEndAfterPadding() 2105 - (mOrientationHelper.getDecoratedStart(target) 2106 + mOrientationHelper.getDecoratedMeasurement(view))); 2107 } else { 2108 scrollToPositionWithOffset(targetPos, 2109 mOrientationHelper.getEndAfterPadding() 2110 - mOrientationHelper.getDecoratedEnd(target)); 2111 } 2112 } else { 2113 if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) { 2114 scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target)); 2115 } else { 2116 scrollToPositionWithOffset(targetPos, 2117 mOrientationHelper.getDecoratedEnd(target) 2118 - mOrientationHelper.getDecoratedMeasurement(view)); 2119 } 2120 } 2121 } 2122 2123 /** 2124 * Helper class that keeps temporary state while {LayoutManager} is filling out the empty 2125 * space. 2126 */ 2127 static class LayoutState { 2128 2129 static final String TAG = "LLM#LayoutState"; 2130 2131 static final int LAYOUT_START = -1; 2132 2133 static final int LAYOUT_END = 1; 2134 2135 static final int INVALID_LAYOUT = Integer.MIN_VALUE; 2136 2137 static final int ITEM_DIRECTION_HEAD = -1; 2138 2139 static final int ITEM_DIRECTION_TAIL = 1; 2140 2141 static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE; 2142 2143 /** 2144 * We may not want to recycle children in some cases (e.g. layout) 2145 */ 2146 boolean mRecycle = true; 2147 2148 /** 2149 * Pixel offset where layout should start 2150 */ 2151 int mOffset; 2152 2153 /** 2154 * Number of pixels that we should fill, in the layout direction. 2155 */ 2156 int mAvailable; 2157 2158 /** 2159 * Current position on the adapter to get the next item. 2160 */ 2161 int mCurrentPosition; 2162 2163 /** 2164 * Defines the direction in which the data adapter is traversed. 2165 * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL} 2166 */ 2167 int mItemDirection; 2168 2169 /** 2170 * Defines the direction in which the layout is filled. 2171 * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END} 2172 */ 2173 int mLayoutDirection; 2174 2175 /** 2176 * Used when LayoutState is constructed in a scrolling state. 2177 * It should be set the amount of scrolling we can make without creating a new view. 2178 * Settings this is required for efficient view recycling. 2179 */ 2180 int mScrollingOffset; 2181 2182 /** 2183 * Used if you want to pre-layout items that are not yet visible. 2184 * The difference with {@link #mAvailable} is that, when recycling, distance laid out for 2185 * {@link #mExtra} is not considered to avoid recycling visible children. 2186 */ 2187 int mExtra = 0; 2188 2189 /** 2190 * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value 2191 * is set to true, we skip removed views since they should not be laid out in post layout 2192 * step. 2193 */ 2194 boolean mIsPreLayout = false; 2195 2196 /** 2197 * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} 2198 * amount. 2199 */ 2200 int mLastScrollDelta; 2201 2202 /** 2203 * When LLM needs to layout particular views, it sets this list in which case, LayoutState 2204 * will only return views from this list and return null if it cannot find an item. 2205 */ 2206 List<RecyclerView.ViewHolder> mScrapList = null; 2207 2208 /** 2209 * Used when there is no limit in how many views can be laid out. 2210 */ 2211 boolean mInfinite; 2212 2213 /** 2214 * @return true if there are more items in the data adapter 2215 */ 2216 boolean hasMore(RecyclerView.State state) { 2217 return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); 2218 } 2219 2220 /** 2221 * Gets the view for the next element that we should layout. 2222 * Also updates current item index to the next item, based on {@link #mItemDirection} 2223 * 2224 * @return The next element that we should layout. 2225 */ 2226 View next(RecyclerView.Recycler recycler) { 2227 if (mScrapList != null) { 2228 return nextViewFromScrapList(); 2229 } 2230 final View view = recycler.getViewForPosition(mCurrentPosition); 2231 mCurrentPosition += mItemDirection; 2232 return view; 2233 } 2234 2235 /** 2236 * Returns the next item from the scrap list. 2237 * <p> 2238 * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection 2239 * 2240 * @return View if an item in the current position or direction exists if not null. 2241 */ 2242 private View nextViewFromScrapList() { 2243 final int size = mScrapList.size(); 2244 for (int i = 0; i < size; i++) { 2245 final View view = mScrapList.get(i).itemView; 2246 final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams(); 2247 if (lp.isItemRemoved()) { 2248 continue; 2249 } 2250 if (mCurrentPosition == lp.getViewLayoutPosition()) { 2251 assignPositionFromScrapList(view); 2252 return view; 2253 } 2254 } 2255 return null; 2256 } 2257 2258 public void assignPositionFromScrapList() { 2259 assignPositionFromScrapList(null); 2260 } 2261 2262 public void assignPositionFromScrapList(View ignore) { 2263 final View closest = nextViewInLimitedList(ignore); 2264 if (closest == null) { 2265 mCurrentPosition = RecyclerView.NO_POSITION; 2266 } else { 2267 mCurrentPosition = ((RecyclerView.LayoutParams) closest.getLayoutParams()) 2268 .getViewLayoutPosition(); 2269 } 2270 } 2271 2272 public View nextViewInLimitedList(View ignore) { 2273 int size = mScrapList.size(); 2274 View closest = null; 2275 int closestDistance = Integer.MAX_VALUE; 2276 if (DEBUG && mIsPreLayout) { 2277 throw new IllegalStateException("Scrap list cannot be used in pre layout"); 2278 } 2279 for (int i = 0; i < size; i++) { 2280 View view = mScrapList.get(i).itemView; 2281 final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams(); 2282 if (view == ignore || lp.isItemRemoved()) { 2283 continue; 2284 } 2285 final int distance = (lp.getViewLayoutPosition() - mCurrentPosition) 2286 * mItemDirection; 2287 if (distance < 0) { 2288 continue; // item is not in current direction 2289 } 2290 if (distance < closestDistance) { 2291 closest = view; 2292 closestDistance = distance; 2293 if (distance == 0) { 2294 break; 2295 } 2296 } 2297 } 2298 return closest; 2299 } 2300 2301 void log() { 2302 Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" 2303 + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection); 2304 } 2305 } 2306 2307 /** 2308 * @hide 2309 */ 2310 @RestrictTo(LIBRARY_GROUP) 2311 public static class SavedState implements Parcelable { 2312 2313 int mAnchorPosition; 2314 2315 int mAnchorOffset; 2316 2317 boolean mAnchorLayoutFromEnd; 2318 2319 public SavedState() { 2320 2321 } 2322 2323 SavedState(Parcel in) { 2324 mAnchorPosition = in.readInt(); 2325 mAnchorOffset = in.readInt(); 2326 mAnchorLayoutFromEnd = in.readInt() == 1; 2327 } 2328 2329 public SavedState(SavedState other) { 2330 mAnchorPosition = other.mAnchorPosition; 2331 mAnchorOffset = other.mAnchorOffset; 2332 mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; 2333 } 2334 2335 boolean hasValidAnchor() { 2336 return mAnchorPosition >= 0; 2337 } 2338 2339 void invalidateAnchor() { 2340 mAnchorPosition = RecyclerView.NO_POSITION; 2341 } 2342 2343 @Override 2344 public int describeContents() { 2345 return 0; 2346 } 2347 2348 @Override 2349 public void writeToParcel(Parcel dest, int flags) { 2350 dest.writeInt(mAnchorPosition); 2351 dest.writeInt(mAnchorOffset); 2352 dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); 2353 } 2354 2355 public static final Parcelable.Creator<SavedState> CREATOR = 2356 new Parcelable.Creator<SavedState>() { 2357 @Override 2358 public SavedState createFromParcel(Parcel in) { 2359 return new SavedState(in); 2360 } 2361 2362 @Override 2363 public SavedState[] newArray(int size) { 2364 return new SavedState[size]; 2365 } 2366 }; 2367 } 2368 2369 /** 2370 * Simple data class to keep Anchor information 2371 */ 2372 static class AnchorInfo { 2373 OrientationHelper mOrientationHelper; 2374 int mPosition; 2375 int mCoordinate; 2376 boolean mLayoutFromEnd; 2377 boolean mValid; 2378 2379 AnchorInfo() { 2380 reset(); 2381 } 2382 2383 void reset() { 2384 mPosition = RecyclerView.NO_POSITION; 2385 mCoordinate = INVALID_OFFSET; 2386 mLayoutFromEnd = false; 2387 mValid = false; 2388 } 2389 2390 /** 2391 * assigns anchor coordinate from the RecyclerView's padding depending on current 2392 * layoutFromEnd value 2393 */ 2394 void assignCoordinateFromPadding() { 2395 mCoordinate = mLayoutFromEnd 2396 ? mOrientationHelper.getEndAfterPadding() 2397 : mOrientationHelper.getStartAfterPadding(); 2398 } 2399 2400 @Override 2401 public String toString() { 2402 return "AnchorInfo{" 2403 + "mPosition=" + mPosition 2404 + ", mCoordinate=" + mCoordinate 2405 + ", mLayoutFromEnd=" + mLayoutFromEnd 2406 + ", mValid=" + mValid 2407 + '}'; 2408 } 2409 2410 boolean isViewValidAsAnchor(View child, RecyclerView.State state) { 2411 RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); 2412 return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0 2413 && lp.getViewLayoutPosition() < state.getItemCount(); 2414 } 2415 2416 public void assignFromViewAndKeepVisibleRect(View child, int position) { 2417 final int spaceChange = mOrientationHelper.getTotalSpaceChange(); 2418 if (spaceChange >= 0) { 2419 assignFromView(child, position); 2420 return; 2421 } 2422 mPosition = position; 2423 if (mLayoutFromEnd) { 2424 final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange; 2425 final int childEnd = mOrientationHelper.getDecoratedEnd(child); 2426 final int previousEndMargin = prevLayoutEnd - childEnd; 2427 mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin; 2428 // ensure we did not push child's top out of bounds because of this 2429 if (previousEndMargin > 0) { // we have room to shift bottom if necessary 2430 final int childSize = mOrientationHelper.getDecoratedMeasurement(child); 2431 final int estimatedChildStart = mCoordinate - childSize; 2432 final int layoutStart = mOrientationHelper.getStartAfterPadding(); 2433 final int previousStartMargin = mOrientationHelper.getDecoratedStart(child) 2434 - layoutStart; 2435 final int startReference = layoutStart + Math.min(previousStartMargin, 0); 2436 final int startMargin = estimatedChildStart - startReference; 2437 if (startMargin < 0) { 2438 // offset to make top visible but not too much 2439 mCoordinate += Math.min(previousEndMargin, -startMargin); 2440 } 2441 } 2442 } else { 2443 final int childStart = mOrientationHelper.getDecoratedStart(child); 2444 final int startMargin = childStart - mOrientationHelper.getStartAfterPadding(); 2445 mCoordinate = childStart; 2446 if (startMargin > 0) { // we have room to fix end as well 2447 final int estimatedEnd = childStart 2448 + mOrientationHelper.getDecoratedMeasurement(child); 2449 final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding() 2450 - spaceChange; 2451 final int previousEndMargin = previousLayoutEnd 2452 - mOrientationHelper.getDecoratedEnd(child); 2453 final int endReference = mOrientationHelper.getEndAfterPadding() 2454 - Math.min(0, previousEndMargin); 2455 final int endMargin = endReference - estimatedEnd; 2456 if (endMargin < 0) { 2457 mCoordinate -= Math.min(startMargin, -endMargin); 2458 } 2459 } 2460 } 2461 } 2462 2463 public void assignFromView(View child, int position) { 2464 if (mLayoutFromEnd) { 2465 mCoordinate = mOrientationHelper.getDecoratedEnd(child) 2466 + mOrientationHelper.getTotalSpaceChange(); 2467 } else { 2468 mCoordinate = mOrientationHelper.getDecoratedStart(child); 2469 } 2470 2471 mPosition = position; 2472 } 2473 } 2474 2475 protected static class LayoutChunkResult { 2476 public int mConsumed; 2477 public boolean mFinished; 2478 public boolean mIgnoreConsumed; 2479 public boolean mFocusable; 2480 2481 void resetInternal() { 2482 mConsumed = 0; 2483 mFinished = false; 2484 mIgnoreConsumed = false; 2485 mFocusable = false; 2486 } 2487 } 2488 } 2489