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