LinearLayoutManager.java revision 245b9720dad47a694d16a1d0f48ad462bc27989f
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific languag`e governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.widget; 18 19import android.content.Context; 20import android.os.Parcel; 21import android.os.Parcelable; 22import android.graphics.PointF; 23import android.support.v4.view.ViewCompat; 24import android.util.Log; 25import android.view.View; 26import android.view.ViewGroup; 27import static android.support.v7.widget.RecyclerView.NO_POSITION; 28 29import java.util.List; 30 31/** 32 * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides 33 * similar functionality to {@link android.widget.ListView}. 34 */ 35public class LinearLayoutManager extends RecyclerView.LayoutManager { 36 37 private static final String TAG = "LinearLayoutManager"; 38 39 private static final boolean DEBUG = false; 40 41 public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; 42 43 public static final int VERTICAL = OrientationHelper.VERTICAL; 44 45 public static final int INVALID_OFFSET = Integer.MIN_VALUE; 46 47 48 /** 49 * While trying to find next view to focus, LinearLayoutManager will not try to scroll more 50 * than 51 * this factor times the total space of the list. If layout is vertical, total space is the 52 * height minus padding, if layout is horizontal, total space is the width minus padding. 53 */ 54 private static final float MAX_SCROLL_FACTOR = 0.33f; 55 56 57 /** 58 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} 59 */ 60 int mOrientation; 61 62 /** 63 * Helper class that keeps temporary layout state. 64 * It does not keep state after layout is complete but we still keep a reference to re-use 65 * the same object. 66 */ 67 private LayoutState mLayoutState; 68 69 /** 70 * Many calculations are made depending on orientation. To keep it clean, this interface 71 * helps {@link LinearLayoutManager} make those decisions. 72 * Based on {@link #mOrientation}, an implementation is lazily created in 73 * {@link #ensureLayoutState} method. 74 */ 75 OrientationHelper mOrientationHelper; 76 77 /** 78 * We need to track this so that we can ignore current position when it changes. 79 */ 80 private boolean mLastStackFromEnd; 81 82 83 /** 84 * Defines if layout should be calculated from end to start. 85 * 86 * @see #mShouldReverseLayout 87 */ 88 private boolean mReverseLayout = false; 89 90 /** 91 * This keeps the final value for how LayoutManager should start laying out views. 92 * It is calculated by checking {@link #getReverseLayout()} and View's layout direction. 93 * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run. 94 */ 95 boolean mShouldReverseLayout = false; 96 97 /** 98 * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and 99 * it supports both orientations. 100 * see {@link android.widget.AbsListView#setStackFromBottom(boolean)} 101 */ 102 private boolean mStackFromEnd = false; 103 104 /** 105 * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. 106 * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} 107 */ 108 private boolean mSmoothScrollbarEnabled = true; 109 110 /** 111 * When LayoutManager needs to scroll to a position, it sets this variable and requests a 112 * layout which will check this variable and re-layout accordingly. 113 */ 114 int mPendingScrollPosition = NO_POSITION; 115 116 /** 117 * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is 118 * called. 119 */ 120 int mPendingScrollPositionOffset = INVALID_OFFSET; 121 122 private boolean mRecycleChildrenOnDetach; 123 124 SavedState mPendingSavedState = null; 125 126 /** 127 * Re-used variable to keep anchor information on re-layout. 128 * Anchor position and coordinate defines the reference point for LLM while doing a layout. 129 * */ 130 final AnchorInfo mAnchorInfo; 131 132 /** 133 * Creates a vertical LinearLayoutManager 134 * 135 * @param context Current context, will be used to access resources. 136 */ 137 public LinearLayoutManager(Context context) { 138 this(context, VERTICAL, false); 139 } 140 141 /** 142 * @param context Current context, will be used to access resources. 143 * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link 144 * #VERTICAL}. 145 * @param reverseLayout When set to true, layouts from end to start. 146 */ 147 public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 148 mAnchorInfo = new AnchorInfo(); 149 setOrientation(orientation); 150 setReverseLayout(reverseLayout); 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 @Override 157 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 158 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 159 ViewGroup.LayoutParams.WRAP_CONTENT); 160 } 161 162 /** 163 * Returns whether LinearLayoutManager will recycle its children when it is detached from 164 * RecyclerView. 165 * 166 * @return true if LinearLayoutManager will recycle its children when it is detached from 167 * RecyclerView. 168 */ 169 public boolean getRecycleChildrenOnDetach() { 170 return mRecycleChildrenOnDetach; 171 } 172 173 /** 174 * Set whether LinearLayoutManager will recycle its children when it is detached from 175 * RecyclerView. 176 * <p> 177 * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set 178 * this flag to <code>true</code> so that views will be avilable to other RecyclerViews 179 * immediately. 180 * <p> 181 * Note that, setting this flag will result in a performance drop if RecyclerView 182 * is restored. 183 * 184 * @param recycleChildrenOnDetach Whether children should be recycled in detach or not. 185 */ 186 public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) { 187 mRecycleChildrenOnDetach = recycleChildrenOnDetach; 188 } 189 190 @Override 191 public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { 192 super.onDetachedFromWindow(view, recycler); 193 if (mRecycleChildrenOnDetach) { 194 removeAndRecycleAllViews(recycler); 195 recycler.clear(); 196 } 197 } 198 199 @Override 200 public Parcelable onSaveInstanceState() { 201 if (mPendingSavedState != null) { 202 return new SavedState(mPendingSavedState); 203 } 204 SavedState state = new SavedState(); 205 if (getChildCount() > 0) { 206 boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout; 207 state.mAnchorLayoutFromEnd = didLayoutFromEnd; 208 if (didLayoutFromEnd) { 209 final View refChild = getChildClosestToEnd(); 210 state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() - 211 mOrientationHelper.getDecoratedEnd(refChild); 212 state.mAnchorPosition = getPosition(refChild); 213 } else { 214 final View refChild = getChildClosestToStart(); 215 state.mAnchorPosition = getPosition(refChild); 216 state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) - 217 mOrientationHelper.getStartAfterPadding(); 218 } 219 } else { 220 state.invalidateAnchor(); 221 } 222 return state; 223 } 224 225 @Override 226 public void onRestoreInstanceState(Parcelable state) { 227 if (state instanceof SavedState) { 228 mPendingSavedState = (SavedState) state; 229 requestLayout(); 230 if (DEBUG) { 231 Log.d(TAG, "loaded saved state"); 232 } 233 } else if (DEBUG) { 234 Log.d(TAG, "invalid saved state class"); 235 } 236 } 237 238 /** 239 * @return true if {@link #getOrientation()} is {@link #HORIZONTAL} 240 */ 241 @Override 242 public boolean canScrollHorizontally() { 243 return mOrientation == HORIZONTAL; 244 } 245 246 /** 247 * @return true if {@link #getOrientation()} is {@link #VERTICAL} 248 */ 249 @Override 250 public boolean canScrollVertically() { 251 return mOrientation == VERTICAL; 252 } 253 254 /** 255 * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)} 256 */ 257 public void setStackFromEnd(boolean stackFromEnd) { 258 assertNotInLayoutOrScroll(null); 259 if (mStackFromEnd == stackFromEnd) { 260 return; 261 } 262 mStackFromEnd = stackFromEnd; 263 requestLayout(); 264 } 265 266 public boolean getStackFromEnd() { 267 return mStackFromEnd; 268 } 269 270 /** 271 * Returns the current orientaion of the layout. 272 * 273 * @return Current orientation. 274 * @see #mOrientation 275 * @see #setOrientation(int) 276 */ 277 public int getOrientation() { 278 return mOrientation; 279 } 280 281 /** 282 * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager} 283 * will do its best to keep scroll position. 284 * 285 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} 286 */ 287 public void setOrientation(int orientation) { 288 if (orientation != HORIZONTAL && orientation != VERTICAL) { 289 throw new IllegalArgumentException("invalid orientation:" + orientation); 290 } 291 assertNotInLayoutOrScroll(null); 292 if (orientation == mOrientation) { 293 return; 294 } 295 mOrientation = orientation; 296 mOrientationHelper = null; 297 requestLayout(); 298 } 299 300 /** 301 * Calculates the view layout order. (e.g. from end to start or start to end) 302 * RTL layout support is applied automatically. So if layout is RTL and 303 * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. 304 */ 305 private void resolveShouldLayoutReverse() { 306 // A == B is the same result, but we rather keep it readable 307 if (mOrientation == VERTICAL || !isLayoutRTL()) { 308 mShouldReverseLayout = mReverseLayout; 309 } else { 310 mShouldReverseLayout = !mReverseLayout; 311 } 312 } 313 314 /** 315 * Returns if views are laid out from the opposite direction of the layout. 316 * 317 * @return If layout is reversed or not. 318 * @see {@link #setReverseLayout(boolean)} 319 */ 320 public boolean getReverseLayout() { 321 return mReverseLayout; 322 } 323 324 /** 325 * Used to reverse item traversal and layout order. 326 * This behaves similar to the layout change for RTL views. When set to true, first item is 327 * laid out at the end of the UI, second item is laid out before it etc. 328 * 329 * For horizontal layouts, it depends on the layout direction. 330 * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will 331 * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout 332 * from LTR. 333 * 334 * If you are looking for the exact same behavior of 335 * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use 336 * {@link #setStackFromEnd(boolean)} 337 */ 338 public void setReverseLayout(boolean reverseLayout) { 339 assertNotInLayoutOrScroll(null); 340 if (reverseLayout == mReverseLayout) { 341 return; 342 } 343 mReverseLayout = reverseLayout; 344 requestLayout(); 345 } 346 347 /** 348 * {@inheritDoc} 349 */ 350 @Override 351 public View findViewByPosition(int position) { 352 final int childCount = getChildCount(); 353 if (childCount == 0) { 354 return null; 355 } 356 final int firstChild = getPosition(getChildAt(0)); 357 final int viewPosition = position - firstChild; 358 if (viewPosition >= 0 && viewPosition < childCount) { 359 return getChildAt(viewPosition); 360 } 361 return null; 362 } 363 364 /** 365 * <p>Returns the amount of extra space that should be laid out by LinearLayoutManager. 366 * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of 367 * items while smooth scrolling and 0 otherwise. You can override this method to implement your 368 * custom layout pre-cache logic.</p> 369 * <p>Laying out invisible elements will eventually come with performance cost. On the other 370 * hand, in places like smooth scrolling to an unknown location, this extra content helps 371 * LayoutManager to calculate a much smoother scrolling; which improves user experience.</p> 372 * <p>You can also use this if you are trying to pre-layout your upcoming views.</p> 373 * 374 * @return The extra space that should be laid out (in pixels). 375 */ 376 protected int getExtraLayoutSpace(RecyclerView.State state) { 377 if (state.hasTargetScrollPosition()) { 378 return mOrientationHelper.getTotalSpace(); 379 } else { 380 return 0; 381 } 382 } 383 384 @Override 385 public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, 386 int position) { 387 LinearSmoothScroller linearSmoothScroller = 388 new LinearSmoothScroller(recyclerView.getContext()) { 389 @Override 390 public PointF computeScrollVectorForPosition(int targetPosition) { 391 return LinearLayoutManager.this 392 .computeScrollVectorForPosition(targetPosition); 393 } 394 }; 395 linearSmoothScroller.setTargetPosition(position); 396 startSmoothScroll(linearSmoothScroller); 397 } 398 399 public PointF computeScrollVectorForPosition(int targetPosition) { 400 if (getChildCount() == 0) { 401 return null; 402 } 403 final int firstChildPos = getPosition(getChildAt(0)); 404 final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1; 405 if (mOrientation == HORIZONTAL) { 406 return new PointF(direction, 0); 407 } else { 408 return new PointF(0, direction); 409 } 410 } 411 412 /** 413 * {@inheritDoc} 414 */ 415 @Override 416 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 417 // layout algorithm: 418 // 1) by checking children and other variables, find an anchor coordinate and an anchor 419 // item position. 420 // 2) fill towards start, stacking from bottom 421 // 3) fill towards end, stacking from top 422 // 4) scroll to fulfill requirements like stack from bottom. 423 // create layout state 424 if (DEBUG) { 425 Log.d(TAG, "is pre layout:" + state.isPreLayout()); 426 } 427 if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { 428 mPendingScrollPosition = mPendingSavedState.mAnchorPosition; 429 } 430 431 ensureLayoutState(); 432 mLayoutState.mRecycle = false; 433 // resolve layout direction 434 resolveShouldLayoutReverse(); 435 436 mAnchorInfo.reset(); 437 mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; 438 // calculate anchor position and coordinate 439 updateAnchorInfoForLayout(state, mAnchorInfo); 440 if (DEBUG) { 441 Log.d(TAG, "Anchor info:" + mAnchorInfo); 442 } 443 444 // LLM may decide to layout items for "extra" pixels to account for scrolling target, 445 // caching or predictive animations. 446 int extraForStart; 447 int extraForEnd; 448 final int extra = getExtraLayoutSpace(state); 449 boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition; 450 if (before == mShouldReverseLayout) { 451 extraForEnd = extra; 452 extraForStart = 0; 453 } else { 454 extraForStart = extra; 455 extraForEnd = 0; 456 } 457 extraForStart += mOrientationHelper.getStartAfterPadding(); 458 extraForEnd += mOrientationHelper.getEndPadding(); 459 if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION && 460 mPendingScrollPositionOffset != INVALID_OFFSET) { 461 // if the child is visible and we are going to move it around, we should layout 462 // extra items in the opposite direction to make sure new items animate nicely 463 // instead of just fading in 464 final View existing = findViewByPosition(mPendingScrollPosition); 465 if (existing != null) { 466 final int current; 467 final int upcomingOffset; 468 if (mShouldReverseLayout) { 469 current = mOrientationHelper.getEndAfterPadding() - 470 mOrientationHelper.getDecoratedEnd(existing); 471 upcomingOffset = current - mPendingScrollPositionOffset; 472 } else { 473 current = mOrientationHelper.getDecoratedStart(existing) 474 - mOrientationHelper.getStartAfterPadding(); 475 upcomingOffset = mPendingScrollPositionOffset - current; 476 } 477 if (upcomingOffset > 0) { 478 extraForStart += upcomingOffset; 479 } else { 480 extraForEnd -= upcomingOffset; 481 } 482 } 483 } 484 int startOffset; 485 int endOffset; 486 onAnchorReady(state, mAnchorInfo); 487 detachAndScrapAttachedViews(recycler); 488 mLayoutState.mIsPreLayout = state.isPreLayout(); 489 if (mAnchorInfo.mLayoutFromEnd) { 490 // fill towards start 491 updateLayoutStateToFillStart(mAnchorInfo); 492 mLayoutState.mExtra = extraForStart; 493 fill(recycler, mLayoutState, state, false); 494 startOffset = mLayoutState.mOffset; 495 if (mLayoutState.mAvailable > 0) { 496 extraForEnd += mLayoutState.mAvailable; 497 } 498 // fill towards end 499 updateLayoutStateToFillEnd(mAnchorInfo); 500 mLayoutState.mExtra = extraForEnd; 501 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; 502 fill(recycler, mLayoutState, state, false); 503 endOffset = mLayoutState.mOffset; 504 } else { 505 // fill towards end 506 updateLayoutStateToFillEnd(mAnchorInfo); 507 mLayoutState.mExtra = extraForEnd; 508 fill(recycler, mLayoutState, state, false); 509 endOffset = mLayoutState.mOffset; 510 if (mLayoutState.mAvailable > 0) { 511 extraForStart += mLayoutState.mAvailable; 512 } 513 // fill towards start 514 updateLayoutStateToFillStart(mAnchorInfo); 515 mLayoutState.mExtra = extraForStart; 516 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; 517 fill(recycler, mLayoutState, state, false); 518 startOffset = mLayoutState.mOffset; 519 } 520 521 // changes may cause gaps on the UI, try to fix them. 522 // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have 523 // changed 524 if (getChildCount() > 0) { 525 // because layout from end may be changed by scroll to position 526 // we re-calculate it. 527 // find which side we should check for gaps. 528 if (mShouldReverseLayout ^ mStackFromEnd) { 529 int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); 530 startOffset += fixOffset; 531 endOffset += fixOffset; 532 fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); 533 startOffset += fixOffset; 534 endOffset += fixOffset; 535 } else { 536 int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); 537 startOffset += fixOffset; 538 endOffset += fixOffset; 539 fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); 540 startOffset += fixOffset; 541 endOffset += fixOffset; 542 } 543 } 544 layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); 545 if (!state.isPreLayout()) { 546 mPendingScrollPosition = NO_POSITION; 547 mPendingScrollPositionOffset = INVALID_OFFSET; 548 mOrientationHelper.onLayoutComplete(); 549 } 550 mLastStackFromEnd = mStackFromEnd; 551 mPendingSavedState = null; // we don't need this anymore 552 if (DEBUG) { 553 validateChildOrder(); 554 } 555 } 556 557 /** 558 * Method called when Anchor position is decided. Extending class can setup accordingly or 559 * even update anchor info if necessary. 560 * 561 * @param state 562 * @param anchorInfo Simple data structure to keep anchor point information for the next layout 563 */ 564 void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) { 565 } 566 567 /** 568 * If necessary, layouts new items for predictive animations 569 */ 570 private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler, 571 RecyclerView.State state, int startOffset, int endOffset) { 572 // If there are scrap children that we did not layout, we need to find where they did go 573 // and layout them accordingly so that animations can work as expected. 574 // This case may happen if new views are added or an existing view expands and pushes 575 // another view out of bounds. 576 if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout() 577 || !supportsPredictiveItemAnimations()) { 578 return; 579 } 580 581 // to make the logic simpler, we calculate the size of children and call fill. 582 int scrapExtraStart = 0, scrapExtraEnd = 0; 583 final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList(); 584 final int scrapSize = scrapList.size(); 585 final int firstChildPos = getPosition(getChildAt(0)); 586 for (int i = 0; i < scrapSize; i++) { 587 RecyclerView.ViewHolder scrap = scrapList.get(i); 588 final int position = scrap.getPosition(); 589 final int direction = position < firstChildPos != mShouldReverseLayout 590 ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; 591 if (direction == LayoutState.LAYOUT_START) { 592 scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); 593 } else { 594 scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); 595 } 596 } 597 598 if (DEBUG) { 599 Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart 600 + " towards start and " + scrapExtraEnd + " towards end"); 601 } 602 mLayoutState.mScrapList = scrapList; 603 if (scrapExtraStart > 0) { 604 View anchor = getChildClosestToStart(); 605 updateLayoutStateToFillStart(getPosition(anchor), startOffset); 606 mLayoutState.mExtra = scrapExtraStart; 607 mLayoutState.mAvailable = 0; 608 mLayoutState.mCurrentPosition += mShouldReverseLayout ? 1 : -1; 609 fill(recycler, mLayoutState, state, false); 610 } 611 612 if (scrapExtraEnd > 0) { 613 View anchor = getChildClosestToEnd(); 614 updateLayoutStateToFillEnd(getPosition(anchor), endOffset); 615 mLayoutState.mExtra = scrapExtraEnd; 616 mLayoutState.mAvailable = 0; 617 mLayoutState.mCurrentPosition += mShouldReverseLayout ? -1 : 1; 618 fill(recycler, mLayoutState, state, false); 619 } 620 mLayoutState.mScrapList = null; 621 } 622 623 private void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) { 624 if (updateAnchorFromPendingData(state, anchorInfo)) { 625 if (DEBUG) { 626 Log.d(TAG, "updated anchor info from pending information"); 627 } 628 return; 629 } 630 631 if (updateAnchorFromChildren(state, anchorInfo)) { 632 if (DEBUG) { 633 Log.d(TAG, "updated anchor info from existing children"); 634 } 635 return; 636 } 637 if (DEBUG) { 638 Log.d(TAG, "deciding anchor info for fresh state"); 639 } 640 anchorInfo.assignCoordinateFromPadding(); 641 anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; 642 } 643 644 /** 645 * Finds an anchor child from existing Views. Most of the time, this is the view closest to 646 * start or end that has a valid position (e.g. not removed). 647 * <p> 648 * If a child has focus, it is given priority. 649 */ 650 private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) { 651 if (getChildCount() == 0) { 652 return false; 653 } 654 View focused = getFocusedChild(); 655 if (focused != null && anchorInfo.assignFromViewIfValid(focused, state)) { 656 if (DEBUG) { 657 Log.d(TAG, "decided anchor child from focused view"); 658 } 659 return true; 660 } 661 662 if (mLastStackFromEnd != mStackFromEnd) { 663 return false; 664 } 665 666 View referenceChild = anchorInfo.mLayoutFromEnd ? findReferenceChildClosestToEnd(state) 667 : findReferenceChildClosestToStart(state); 668 if (referenceChild != null) { 669 anchorInfo.assignFromView(referenceChild); 670 // If all visible views are removed in 1 pass, reference child might be out of bounds. 671 // If that is the case, offset it back to 0 so that we use these pre-layout children. 672 if (!state.isPreLayout() && supportsPredictiveItemAnimations()) { 673 // validate this child is at least partially visible. if not, offset it to start 674 final boolean notVisible = 675 mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper 676 .getEndAfterPadding() 677 || mOrientationHelper.getDecoratedEnd(referenceChild) 678 < mOrientationHelper.getStartAfterPadding(); 679 if (notVisible) { 680 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd 681 ? mOrientationHelper.getEndAfterPadding() 682 : mOrientationHelper.getStartAfterPadding(); 683 } 684 } 685 return true; 686 } 687 return false; 688 } 689 690 /** 691 * If there is a pending scroll position or saved states, updates the anchor info from that 692 * data and returns true 693 */ 694 private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { 695 if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) { 696 return false; 697 } 698 // validate scroll position 699 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { 700 mPendingScrollPosition = NO_POSITION; 701 mPendingScrollPositionOffset = INVALID_OFFSET; 702 if (DEBUG) { 703 Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition); 704 } 705 return false; 706 } 707 708 // if child is visible, try to make it a reference child and ensure it is fully visible. 709 // if child is not visible, align it depending on its virtual position. 710 anchorInfo.mPosition = mPendingScrollPosition; 711 if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { 712 // Anchor offset depends on how that child was laid out. Here, we update it 713 // according to our current view bounds 714 anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; 715 if (anchorInfo.mLayoutFromEnd) { 716 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - 717 mPendingSavedState.mAnchorOffset; 718 } else { 719 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + 720 mPendingSavedState.mAnchorOffset; 721 } 722 return true; 723 } 724 725 if (mPendingScrollPositionOffset == INVALID_OFFSET) { 726 View child = findViewByPosition(mPendingScrollPosition); 727 if (child != null) { 728 final int childSize = mOrientationHelper.getDecoratedMeasurement(child); 729 if (childSize > mOrientationHelper.getTotalSpace()) { 730 // item does not fit. fix depending on layout direction 731 anchorInfo.assignCoordinateFromPadding(); 732 return true; 733 } 734 final int startGap = mOrientationHelper.getDecoratedStart(child) 735 - mOrientationHelper.getStartAfterPadding(); 736 if (startGap < 0) { 737 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding(); 738 anchorInfo.mLayoutFromEnd = false; 739 return true; 740 } 741 final int endGap = mOrientationHelper.getEndAfterPadding() - 742 mOrientationHelper.getDecoratedEnd(child); 743 if (endGap < 0) { 744 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding(); 745 anchorInfo.mLayoutFromEnd = true; 746 return true; 747 } 748 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd 749 ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper 750 .getTotalSpaceChange()) 751 : mOrientationHelper.getDecoratedStart(child); 752 } else { // item is not visible. 753 if (getChildCount() > 0) { 754 // get position of any child, does not matter 755 int pos = getPosition(getChildAt(0)); 756 anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos 757 == mShouldReverseLayout; 758 } 759 anchorInfo.assignCoordinateFromPadding(); 760 } 761 return true; 762 } 763 // override layout from end values for consistency 764 anchorInfo.mLayoutFromEnd = mShouldReverseLayout; 765 if (mShouldReverseLayout) { 766 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - 767 mPendingScrollPositionOffset; 768 } else { 769 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() + 770 mPendingScrollPositionOffset; 771 } 772 return true; 773 } 774 775 /** 776 * @return The final offset amount for children 777 */ 778 private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler, 779 RecyclerView.State state, boolean canOffsetChildren) { 780 int gap = mOrientationHelper.getEndAfterPadding() - endOffset; 781 int fixOffset = 0; 782 if (gap > 0) { 783 fixOffset = -scrollBy(-gap, recycler, state); 784 } else { 785 return 0; // nothing to fix 786 } 787 // move offset according to scroll amount 788 endOffset += fixOffset; 789 if (canOffsetChildren) { 790 // re-calculate gap, see if we could fix it 791 gap = mOrientationHelper.getEndAfterPadding() - endOffset; 792 if (gap > 0) { 793 mOrientationHelper.offsetChildren(gap); 794 return gap + fixOffset; 795 } 796 } 797 return fixOffset; 798 } 799 800 /** 801 * @return The final offset amount for children 802 */ 803 private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, 804 RecyclerView.State state, boolean canOffsetChildren) { 805 int gap = startOffset - mOrientationHelper.getStartAfterPadding(); 806 int fixOffset = 0; 807 if (gap > 0) { 808 // check if we should fix this gap. 809 fixOffset = -scrollBy(gap, recycler, state); 810 } else { 811 return 0; // nothing to fix 812 } 813 startOffset += fixOffset; 814 if (canOffsetChildren) { 815 // re-calculate gap, see if we could fix it 816 gap = startOffset - mOrientationHelper.getStartAfterPadding(); 817 if (gap > 0) { 818 mOrientationHelper.offsetChildren(-gap); 819 return fixOffset - gap; 820 } 821 } 822 return fixOffset; 823 } 824 825 private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) { 826 updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); 827 } 828 829 private void updateLayoutStateToFillEnd(int itemPosition, int offset) { 830 mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset; 831 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : 832 LayoutState.ITEM_DIRECTION_TAIL; 833 mLayoutState.mCurrentPosition = itemPosition; 834 mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; 835 mLayoutState.mOffset = offset; 836 mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; 837 } 838 839 private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) { 840 updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate); 841 } 842 843 private void updateLayoutStateToFillStart(int itemPosition, int offset) { 844 mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding(); 845 mLayoutState.mCurrentPosition = itemPosition; 846 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : 847 LayoutState.ITEM_DIRECTION_HEAD; 848 mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START; 849 mLayoutState.mOffset = offset; 850 mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; 851 852 } 853 854 protected boolean isLayoutRTL() { 855 return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; 856 } 857 858 void ensureLayoutState() { 859 if (mLayoutState == null) { 860 mLayoutState = new LayoutState(); 861 } 862 if (mOrientationHelper == null) { 863 mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); 864 } 865 } 866 867 /** 868 * <p>Scroll the RecyclerView to make the position visible.</p> 869 * 870 * <p>RecyclerView will scroll the minimum amount that is necessary to make the 871 * target position visible. If you are looking for a similar behavior to 872 * {@link android.widget.ListView#setSelection(int)} or 873 * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use 874 * {@link #scrollToPositionWithOffset(int, int)}.</p> 875 * 876 * <p>Note that scroll position change will not be reflected until the next layout call.</p> 877 * 878 * @param position Scroll to this adapter position 879 * @see #scrollToPositionWithOffset(int, int) 880 */ 881 @Override 882 public void scrollToPosition(int position) { 883 mPendingScrollPosition = position; 884 mPendingScrollPositionOffset = INVALID_OFFSET; 885 if (mPendingSavedState != null) { 886 mPendingSavedState.invalidateAnchor(); 887 } 888 requestLayout(); 889 } 890 891 /** 892 * Scroll to the specified adapter position with the given offset from resolved layout 893 * start. Resolved layout start depends on {@link #getReverseLayout()}, 894 * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}. 895 * <p> 896 * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling 897 * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that 898 * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom. 899 * <p> 900 * Note that scroll position change will not be reflected until the next layout call. 901 * 902 * <p> 903 * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. 904 * 905 * @param position Index (starting at 0) of the reference item. 906 * @param offset The distance (in pixels) between the start edge of the item view and 907 * start edge of the RecyclerView. 908 * @see #setReverseLayout(boolean) 909 * @see #scrollToPosition(int) 910 */ 911 public void scrollToPositionWithOffset(int position, int offset) { 912 mPendingScrollPosition = position; 913 mPendingScrollPositionOffset = offset; 914 if (mPendingSavedState != null) { 915 mPendingSavedState.invalidateAnchor(); 916 } 917 requestLayout(); 918 } 919 920 921 /** 922 * {@inheritDoc} 923 */ 924 @Override 925 public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, 926 RecyclerView.State state) { 927 if (mOrientation == VERTICAL) { 928 return 0; 929 } 930 return scrollBy(dx, recycler, state); 931 } 932 933 /** 934 * {@inheritDoc} 935 */ 936 @Override 937 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, 938 RecyclerView.State state) { 939 if (mOrientation == HORIZONTAL) { 940 return 0; 941 } 942 return scrollBy(dy, recycler, state); 943 } 944 945 @Override 946 public int computeHorizontalScrollOffset(RecyclerView.State state) { 947 return computeScrollOffset(state); 948 } 949 950 @Override 951 public int computeVerticalScrollOffset(RecyclerView.State state) { 952 return computeScrollOffset(state); 953 } 954 955 @Override 956 public int computeHorizontalScrollExtent(RecyclerView.State state) { 957 return computeScrollExtent(state); 958 } 959 960 @Override 961 public int computeVerticalScrollExtent(RecyclerView.State state) { 962 return computeScrollExtent(state); 963 } 964 965 @Override 966 public int computeHorizontalScrollRange(RecyclerView.State state) { 967 return computeScrollRange(state); 968 } 969 970 @Override 971 public int computeVerticalScrollRange(RecyclerView.State state) { 972 return computeScrollRange(state); 973 } 974 975 private int computeScrollOffset(RecyclerView.State state) { 976 if (getChildCount() == 0) { 977 return 0; 978 } 979 return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper, 980 getChildClosestToStart(), getChildClosestToEnd(), this, 981 mSmoothScrollbarEnabled, mShouldReverseLayout); 982 } 983 984 private int computeScrollExtent(RecyclerView.State state) { 985 if (getChildCount() == 0) { 986 return 0; 987 } 988 return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper, 989 getChildClosestToStart(), getChildClosestToEnd(), this, 990 mSmoothScrollbarEnabled); 991 } 992 993 private int computeScrollRange(RecyclerView.State state) { 994 if (getChildCount() == 0) { 995 return 0; 996 } 997 return ScrollbarHelper.computeScrollRange(state, mOrientationHelper, 998 getChildClosestToStart(), getChildClosestToEnd(), this, 999 mSmoothScrollbarEnabled); 1000 } 1001 1002 /** 1003 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed 1004 * based on the number of visible pixels in the visible items. This however assumes that all 1005 * list items have similar or equal widths or heights (depending on list orientation). 1006 * If you use a list in which items have different dimensions, the scrollbar will change 1007 * appearance as the user scrolls through the list. To avoid this issue, you need to disable 1008 * this property. 1009 * 1010 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based 1011 * solely on the number of items in the adapter and the position of the visible items inside 1012 * the adapter. This provides a stable scrollbar as the user navigates through a list of items 1013 * with varying widths / heights. 1014 * 1015 * @param enabled Whether or not to enable smooth scrollbar. 1016 * 1017 * @see #setSmoothScrollbarEnabled(boolean) 1018 */ 1019 public void setSmoothScrollbarEnabled(boolean enabled) { 1020 mSmoothScrollbarEnabled = enabled; 1021 } 1022 1023 /** 1024 * Returns the current state of the smooth scrollbar feature. It is enabled by default. 1025 * 1026 * @return True if smooth scrollbar is enabled, false otherwise. 1027 * 1028 * @see #setSmoothScrollbarEnabled(boolean) 1029 */ 1030 public boolean isSmoothScrollbarEnabled() { 1031 return mSmoothScrollbarEnabled; 1032 } 1033 1034 private void updateLayoutState(int layoutDirection, int requiredSpace, 1035 boolean canUseExistingSpace, RecyclerView.State state) { 1036 mLayoutState.mExtra = getExtraLayoutSpace(state); 1037 mLayoutState.mLayoutDirection = layoutDirection; 1038 int fastScrollSpace; 1039 if (layoutDirection == LayoutState.LAYOUT_END) { 1040 mLayoutState.mExtra += mOrientationHelper.getEndPadding(); 1041 // get the first child in the direction we are going 1042 final View child = getChildClosestToEnd(); 1043 // the direction in which we are traversing children 1044 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD 1045 : LayoutState.ITEM_DIRECTION_TAIL; 1046 mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; 1047 mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child); 1048 // calculate how much we can scroll without adding new children (independent of layout) 1049 fastScrollSpace = mOrientationHelper.getDecoratedEnd(child) 1050 - mOrientationHelper.getEndAfterPadding(); 1051 1052 } else { 1053 final View child = getChildClosestToStart(); 1054 mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding(); 1055 mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL 1056 : LayoutState.ITEM_DIRECTION_HEAD; 1057 mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; 1058 mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); 1059 fastScrollSpace = -mOrientationHelper.getDecoratedStart(child) 1060 + mOrientationHelper.getStartAfterPadding(); 1061 } 1062 mLayoutState.mAvailable = requiredSpace; 1063 if (canUseExistingSpace) { 1064 mLayoutState.mAvailable -= fastScrollSpace; 1065 } 1066 mLayoutState.mScrollingOffset = fastScrollSpace; 1067 } 1068 1069 int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { 1070 if (getChildCount() == 0 || dy == 0) { 1071 return 0; 1072 } 1073 mLayoutState.mRecycle = true; 1074 ensureLayoutState(); 1075 final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; 1076 final int absDy = Math.abs(dy); 1077 updateLayoutState(layoutDirection, absDy, true, state); 1078 final int freeScroll = mLayoutState.mScrollingOffset; 1079 final int consumed = freeScroll + fill(recycler, mLayoutState, state, false); 1080 if (consumed < 0) { 1081 if (DEBUG) { 1082 Log.d(TAG, "Don't have any more elements to scroll"); 1083 } 1084 return 0; 1085 } 1086 final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; 1087 mOrientationHelper.offsetChildren(-scrolled); 1088 if (DEBUG) { 1089 Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled); 1090 } 1091 return scrolled; 1092 } 1093 1094 @Override 1095 public void assertNotInLayoutOrScroll(String message) { 1096 if (mPendingSavedState == null) { 1097 super.assertNotInLayoutOrScroll(message); 1098 } 1099 } 1100 1101 /** 1102 * Recycles children between given indices. 1103 * 1104 * @param startIndex inclusive 1105 * @param endIndex exclusive 1106 */ 1107 private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { 1108 if (startIndex == endIndex) { 1109 return; 1110 } 1111 if (DEBUG) { 1112 Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items"); 1113 } 1114 if (endIndex > startIndex) { 1115 for (int i = endIndex - 1; i >= startIndex; i--) { 1116 removeAndRecycleViewAt(i, recycler); 1117 } 1118 } else { 1119 for (int i = startIndex; i > endIndex; i--) { 1120 removeAndRecycleViewAt(i, recycler); 1121 } 1122 } 1123 } 1124 1125 /** 1126 * Recycles views that went out of bounds after scrolling towards the end of the layout. 1127 * 1128 * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} 1129 * @param dt This can be used to add additional padding to the visible area. This is used 1130 * to 1131 * detect children that will go out of bounds after scrolling, without actually 1132 * moving them. 1133 */ 1134 private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { 1135 if (dt < 0) { 1136 if (DEBUG) { 1137 Log.d(TAG, "Called recycle from start with a negative value. This might happen" 1138 + " during layout changes but may be sign of a bug"); 1139 } 1140 return; 1141 } 1142 // ignore padding, ViewGroup may not clip children. 1143 final int limit = dt; 1144 final int childCount = getChildCount(); 1145 if (mShouldReverseLayout) { 1146 for (int i = childCount - 1; i >= 0; i--) { 1147 View child = getChildAt(i); 1148 if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here 1149 recycleChildren(recycler, childCount - 1, i); 1150 return; 1151 } 1152 } 1153 } else { 1154 for (int i = 0; i < childCount; i++) { 1155 View child = getChildAt(i); 1156 if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here 1157 recycleChildren(recycler, 0, i); 1158 return; 1159 } 1160 } 1161 } 1162 } 1163 1164 1165 /** 1166 * Recycles views that went out of bounds after scrolling towards the start of the layout. 1167 * 1168 * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} 1169 * @param dt This can be used to add additional padding to the visible area. This is used 1170 * to detect children that will go out of bounds after scrolling, without 1171 * actually moving them. 1172 */ 1173 private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { 1174 final int childCount = getChildCount(); 1175 if (dt < 0) { 1176 if (DEBUG) { 1177 Log.d(TAG, "Called recycle from end with a negative value. This might happen" 1178 + " during layout changes but may be sign of a bug"); 1179 } 1180 return; 1181 } 1182 final int limit = mOrientationHelper.getEnd() - dt; 1183 if (mShouldReverseLayout) { 1184 for (int i = 0; i < childCount; i++) { 1185 View child = getChildAt(i); 1186 if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here 1187 recycleChildren(recycler, 0, i); 1188 return; 1189 } 1190 } 1191 } else { 1192 for (int i = childCount - 1; i >= 0; i--) { 1193 View child = getChildAt(i); 1194 if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here 1195 recycleChildren(recycler, childCount - 1, i); 1196 return; 1197 } 1198 } 1199 } 1200 1201 } 1202 1203 /** 1204 * Helper method to call appropriate recycle method depending on current layout direction 1205 * 1206 * @param recycler Current recycler that is attached to RecyclerView 1207 * @param layoutState Current layout state. Right now, this object does not change but 1208 * we may consider moving it out of this view so passing around as a 1209 * parameter for now, rather than accessing {@link #mLayoutState} 1210 * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int) 1211 * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int) 1212 * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection 1213 */ 1214 private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { 1215 if (!layoutState.mRecycle) { 1216 return; 1217 } 1218 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1219 recycleViewsFromEnd(recycler, layoutState.mScrollingOffset); 1220 } else { 1221 recycleViewsFromStart(recycler, layoutState.mScrollingOffset); 1222 } 1223 } 1224 1225 /** 1226 * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly 1227 * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager} 1228 * and with little change, can be made publicly available as a helper class. 1229 * 1230 * @param recycler Current recycler that is attached to RecyclerView 1231 * @param layoutState Configuration on how we should fill out the available space. 1232 * @param state Context passed by the RecyclerView to control scroll steps. 1233 * @param stopOnFocusable If true, filling stops in the first focusable new child 1234 * @return Number of pixels that it added. Useful for scoll functions. 1235 */ 1236 int fill(RecyclerView.Recycler recycler, LayoutState layoutState, 1237 RecyclerView.State state, boolean stopOnFocusable) { 1238 // max offset we should set is mFastScroll + available 1239 final int start = layoutState.mAvailable; 1240 if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { 1241 // TODO ugly bug fix. should not happen 1242 if (layoutState.mAvailable < 0) { 1243 layoutState.mScrollingOffset += layoutState.mAvailable; 1244 } 1245 recycleByLayoutState(recycler, layoutState); 1246 } 1247 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; 1248 LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); 1249 while (remainingSpace > 0 && layoutState.hasMore(state)) { 1250 layoutChunkResult.resetInternal(); 1251 layoutChunk(recycler, state, layoutState, layoutChunkResult); 1252 if (layoutChunkResult.mFinished) { 1253 break; 1254 } 1255 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; 1256 /** 1257 * Consume the available space if: 1258 * * layoutChunk did not request to be ignored 1259 * * OR we are laying out scrap children 1260 * * OR we are not doing pre-layout 1261 */ 1262 if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null 1263 || !state.isPreLayout()) { 1264 layoutState.mAvailable -= layoutChunkResult.mConsumed; 1265 // we keep a separate remaining space because mAvailable is important for recycling 1266 remainingSpace -= layoutChunkResult.mConsumed; 1267 } 1268 1269 if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { 1270 layoutState.mScrollingOffset += layoutChunkResult.mConsumed; 1271 if (layoutState.mAvailable < 0) { 1272 layoutState.mScrollingOffset += layoutState.mAvailable; 1273 } 1274 recycleByLayoutState(recycler, layoutState); 1275 } 1276 if (stopOnFocusable && layoutChunkResult.mFocusable) { 1277 break; 1278 } 1279 } 1280 if (DEBUG) { 1281 validateChildOrder(); 1282 } 1283 return start - layoutState.mAvailable; 1284 } 1285 1286 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, 1287 LayoutState layoutState, LayoutChunkResult result) { 1288 View view = layoutState.next(recycler); 1289 if (view == null) { 1290 if (DEBUG && layoutState.mScrapList == null) { 1291 throw new RuntimeException("received null view when unexpected"); 1292 } 1293 // if we are laying out views in scrap, this may return null which means there is 1294 // no more items to layout. 1295 result.mFinished = true; 1296 return; 1297 } 1298 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 1299 if (layoutState.mScrapList == null) { 1300 if (mShouldReverseLayout == (layoutState.mLayoutDirection 1301 == LayoutState.LAYOUT_START)) { 1302 addView(view); 1303 } else { 1304 addView(view, 0); 1305 } 1306 } else { 1307 if (mShouldReverseLayout == (layoutState.mLayoutDirection 1308 == LayoutState.LAYOUT_START)) { 1309 addDisappearingView(view); 1310 } else { 1311 addDisappearingView(view, 0); 1312 } 1313 } 1314 measureChildWithMargins(view, 0, 0); 1315 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); 1316 int left, top, right, bottom; 1317 if (mOrientation == VERTICAL) { 1318 if (isLayoutRTL()) { 1319 right = getWidth() - getPaddingRight(); 1320 left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); 1321 } else { 1322 left = getPaddingLeft(); 1323 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); 1324 } 1325 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1326 bottom = layoutState.mOffset; 1327 top = layoutState.mOffset - result.mConsumed; 1328 } else { 1329 top = layoutState.mOffset; 1330 bottom = layoutState.mOffset + result.mConsumed; 1331 } 1332 } else { 1333 top = getPaddingTop(); 1334 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); 1335 1336 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { 1337 right = layoutState.mOffset; 1338 left = layoutState.mOffset - result.mConsumed; 1339 } else { 1340 left = layoutState.mOffset; 1341 right = layoutState.mOffset + result.mConsumed; 1342 } 1343 } 1344 // We calculate everything with View's bounding box (which includes decor and margins) 1345 // To calculate correct layout position, we subtract margins. 1346 layoutDecorated(view, left + params.leftMargin, top + params.topMargin, 1347 right - params.rightMargin, bottom - params.bottomMargin); 1348 if (DEBUG) { 1349 Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" 1350 + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" 1351 + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); 1352 } 1353 // Consume the available space if the view is not removed OR changed 1354 if (params.isItemRemoved() || params.isItemChanged()) { 1355 result.mIgnoreConsumed = true; 1356 } 1357 result.mFocusable = view.isFocusable(); 1358 } 1359 1360 /** 1361 * Converts a focusDirection to orientation. 1362 * 1363 * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 1364 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 1365 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 1366 * or 0 for not applicable 1367 * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction 1368 * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. 1369 */ 1370 private int convertFocusDirectionToLayoutDirection(int focusDirection) { 1371 switch (focusDirection) { 1372 case View.FOCUS_BACKWARD: 1373 return LayoutState.LAYOUT_START; 1374 case View.FOCUS_FORWARD: 1375 return LayoutState.LAYOUT_END; 1376 case View.FOCUS_UP: 1377 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START 1378 : LayoutState.INVALID_LAYOUT; 1379 case View.FOCUS_DOWN: 1380 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END 1381 : LayoutState.INVALID_LAYOUT; 1382 case View.FOCUS_LEFT: 1383 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START 1384 : LayoutState.INVALID_LAYOUT; 1385 case View.FOCUS_RIGHT: 1386 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END 1387 : LayoutState.INVALID_LAYOUT; 1388 default: 1389 if (DEBUG) { 1390 Log.d(TAG, "Unknown focus request:" + focusDirection); 1391 } 1392 return LayoutState.INVALID_LAYOUT; 1393 } 1394 1395 } 1396 1397 /** 1398 * Convenience method to find the child closes to start. Caller should check it has enough 1399 * children. 1400 * 1401 * @return The child closes to start of the layout from user's perspective. 1402 */ 1403 private View getChildClosestToStart() { 1404 return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0); 1405 } 1406 1407 /** 1408 * Convenience method to find the child closes to end. Caller should check it has enough 1409 * children. 1410 * 1411 * @return The child closes to end of the layout from user's perspective. 1412 */ 1413 private View getChildClosestToEnd() { 1414 return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1); 1415 } 1416 1417 1418 /** 1419 * Among the children that are suitable to be considered as an anchor child, returns the one 1420 * closest to the end of the layout. 1421 * <p> 1422 * Due to ambiguous adapter updates or children being removed, some children's positions may be 1423 * invalid. This method is a best effort to find a position within adapter bounds if possible. 1424 * <p> 1425 * It also prioritizes children that are within the visible bounds. 1426 * @return A View that can be used an an anchor View. 1427 */ 1428 private View findReferenceChildClosestToEnd(RecyclerView.State state) { 1429 return mShouldReverseLayout ? findFirstReferenceChild(state.getItemCount()) : 1430 findLastReferenceChild(state.getItemCount()); 1431 } 1432 1433 /** 1434 * Among the children that are suitable to be considered as an anchor child, returns the one 1435 * closest to the start of the layout. 1436 * <p> 1437 * Due to ambiguous adapter updates or children being removed, some children's positions may be 1438 * invalid. This method is a best effort to find a position within adapter bounds if possible. 1439 * <p> 1440 * It also prioritizes children that are within the visible bounds. 1441 * 1442 * @return A View that can be used an an anchor View. 1443 */ 1444 private View findReferenceChildClosestToStart(RecyclerView.State state) { 1445 return mShouldReverseLayout ? findLastReferenceChild(state.getItemCount()) : 1446 findFirstReferenceChild(state.getItemCount()); 1447 } 1448 1449 private View findFirstReferenceChild(int itemCount) { 1450 return findReferenceChild(0, getChildCount(), itemCount); 1451 } 1452 1453 private View findLastReferenceChild(int itemCount) { 1454 return findReferenceChild(getChildCount() - 1, -1, itemCount); 1455 } 1456 1457 private View findReferenceChild(int start, int end, int itemCount) { 1458 View invalidMatch = null; 1459 View outOfBoundsMatch = null; 1460 final int boundsStart = mOrientationHelper.getStartAfterPadding(); 1461 final int boundsEnd = mOrientationHelper.getEndAfterPadding(); 1462 final int diff = end > start ? 1 : -1; 1463 for (int i = start; i != end; i += diff) { 1464 final View view = getChildAt(i); 1465 final int position = getPosition(view); 1466 if (position >= 0 && position < itemCount) { 1467 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) { 1468 if (invalidMatch == null) { 1469 invalidMatch = view; // removed item, least preferred 1470 } 1471 } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd || 1472 mOrientationHelper.getDecoratedEnd(view) < boundsStart) { 1473 if (outOfBoundsMatch == null) { 1474 outOfBoundsMatch = view; // item is not visible, less preferred 1475 } 1476 } else { 1477 return view; 1478 } 1479 } 1480 } 1481 return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch; 1482 } 1483 1484 /** 1485 * Returns the adapter position of the first visible view. 1486 * <p> 1487 * Note that, this value is not affected by layout orientation or item order traversal. 1488 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, 1489 * not in the layout. 1490 * <p> 1491 * If RecyclerView has item decorators, they will be considered in calculations as well. 1492 * <p> 1493 * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views 1494 * are ignored in this method. 1495 * 1496 * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if 1497 * there aren't any visible items. 1498 * @see #findFirstCompletelyVisibleItemPosition() 1499 * @see #findLastVisibleItemPosition() 1500 */ 1501 public int findFirstVisibleItemPosition() { 1502 final View child = findOneVisibleChild(0, getChildCount(), false); 1503 return child == null ? NO_POSITION : getPosition(child); 1504 } 1505 1506 /** 1507 * Returns the adapter position of the first fully visible view. 1508 * <p> 1509 * Note that bounds check is only performed in the current orientation. That means, if 1510 * LinearLayoutManager is horizontal, it will only check the view's left and right edges. 1511 * 1512 * @return The adapter position of the first fully visible item or 1513 * {@link RecyclerView#NO_POSITION} if there aren't any visible items. 1514 * @see #findFirstVisibleItemPosition() 1515 * @see #findLastCompletelyVisibleItemPosition() 1516 */ 1517 public int findFirstCompletelyVisibleItemPosition() { 1518 final View child = findOneVisibleChild(0, getChildCount(), true); 1519 return child == null ? NO_POSITION : getPosition(child); 1520 } 1521 1522 /** 1523 * Returns the adapter position of the last visible view. 1524 * <p> 1525 * Note that, this value is not affected by layout orientation or item order traversal. 1526 * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, 1527 * not in the layout. 1528 * <p> 1529 * If RecyclerView has item decorators, they will be considered in calculations as well. 1530 * <p> 1531 * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views 1532 * are ignored in this method. 1533 * 1534 * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if 1535 * there aren't any visible items. 1536 * @see #findLastCompletelyVisibleItemPosition() 1537 * @see #findFirstVisibleItemPosition() 1538 */ 1539 public int findLastVisibleItemPosition() { 1540 final View child = findOneVisibleChild(getChildCount() - 1, -1, false); 1541 return child == null ? NO_POSITION : getPosition(child); 1542 } 1543 1544 /** 1545 * Returns the adapter position of the last fully visible view. 1546 * <p> 1547 * Note that bounds check is only performed in the current orientation. That means, if 1548 * LinearLayoutManager is horizontal, it will only check the view's left and right edges. 1549 * 1550 * @return The adapter position of the last fully visible view or 1551 * {@link RecyclerView#NO_POSITION} if there aren't any visible items. 1552 * @see #findLastVisibleItemPosition() 1553 * @see #findFirstCompletelyVisibleItemPosition() 1554 */ 1555 public int findLastCompletelyVisibleItemPosition() { 1556 final View child = findOneVisibleChild(getChildCount() - 1, -1, true); 1557 return child == null ? NO_POSITION : getPosition(child); 1558 } 1559 1560 View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { 1561 final int start = mOrientationHelper.getStartAfterPadding(); 1562 final int end = mOrientationHelper.getEndAfterPadding(); 1563 final int next = toIndex > fromIndex ? 1 : -1; 1564 for (int i = fromIndex; i != toIndex; i+=next) { 1565 final View child = getChildAt(i); 1566 final int childStart = mOrientationHelper.getDecoratedStart(child); 1567 final int childEnd = mOrientationHelper.getDecoratedEnd(child); 1568 if (childStart < end && childEnd > start) { 1569 if (completelyVisible) { 1570 if (childStart >= start && childEnd <= end) { 1571 return child; 1572 } 1573 } else { 1574 return child; 1575 } 1576 } 1577 } 1578 return null; 1579 } 1580 1581 @Override 1582 public View onFocusSearchFailed(View focused, int focusDirection, 1583 RecyclerView.Recycler recycler, RecyclerView.State state) { 1584 resolveShouldLayoutReverse(); 1585 if (getChildCount() == 0) { 1586 return null; 1587 } 1588 1589 final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection); 1590 if (layoutDir == LayoutState.INVALID_LAYOUT) { 1591 return null; 1592 } 1593 final View referenceChild; 1594 if (layoutDir == LayoutState.LAYOUT_START) { 1595 referenceChild = findReferenceChildClosestToStart(state); 1596 } else { 1597 referenceChild = findReferenceChildClosestToEnd(state); 1598 } 1599 if (referenceChild == null) { 1600 if (DEBUG) { 1601 Log.d(TAG, 1602 "Cannot find a child with a valid position to be used for focus search."); 1603 } 1604 return null; 1605 } 1606 ensureLayoutState(); 1607 final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); 1608 updateLayoutState(layoutDir, maxScroll, false, state); 1609 mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; 1610 mLayoutState.mRecycle = false; 1611 fill(recycler, mLayoutState, state, true); 1612 final View nextFocus; 1613 if (layoutDir == LayoutState.LAYOUT_START) { 1614 nextFocus = getChildClosestToStart(); 1615 } else { 1616 nextFocus = getChildClosestToEnd(); 1617 } 1618 if (nextFocus == referenceChild || !nextFocus.isFocusable()) { 1619 return null; 1620 } 1621 return nextFocus; 1622 } 1623 1624 /** 1625 * Used for debugging. 1626 * Logs the internal representation of children to default logger. 1627 */ 1628 private void logChildren() { 1629 Log.d(TAG, "internal representation of views on the screen"); 1630 for (int i = 0; i < getChildCount(); i++) { 1631 View child = getChildAt(i); 1632 Log.d(TAG, "item " + getPosition(child) + ", coord:" 1633 + mOrientationHelper.getDecoratedStart(child)); 1634 } 1635 Log.d(TAG, "=============="); 1636 } 1637 1638 /** 1639 * Used for debugging. 1640 * Validates that child views are laid out in correct order. This is important because rest of 1641 * the algorithm relies on this constraint. 1642 * 1643 * In default layout, child 0 should be closest to screen position 0 and last child should be 1644 * closest to position WIDTH or HEIGHT. 1645 * In reverse layout, last child should be closes to screen position 0 and first child should 1646 * be closest to position WIDTH or HEIGHT 1647 */ 1648 void validateChildOrder() { 1649 Log.d(TAG, "validating child count " + getChildCount()); 1650 if (getChildCount() < 1) { 1651 return; 1652 } 1653 int lastPos = getPosition(getChildAt(0)); 1654 int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0)); 1655 if (mShouldReverseLayout) { 1656 for (int i = 1; i < getChildCount(); i++) { 1657 View child = getChildAt(i); 1658 int pos = getPosition(child); 1659 int screenLoc = mOrientationHelper.getDecoratedStart(child); 1660 if (pos < lastPos) { 1661 logChildren(); 1662 throw new RuntimeException("detected invalid position. loc invalid? " + 1663 (screenLoc < lastScreenLoc)); 1664 } 1665 if (screenLoc > lastScreenLoc) { 1666 logChildren(); 1667 throw new RuntimeException("detected invalid location"); 1668 } 1669 } 1670 } else { 1671 for (int i = 1; i < getChildCount(); i++) { 1672 View child = getChildAt(i); 1673 int pos = getPosition(child); 1674 int screenLoc = mOrientationHelper.getDecoratedStart(child); 1675 if (pos < lastPos) { 1676 logChildren(); 1677 throw new RuntimeException("detected invalid position. loc invalid? " + 1678 (screenLoc < lastScreenLoc)); 1679 } 1680 if (screenLoc < lastScreenLoc) { 1681 logChildren(); 1682 throw new RuntimeException("detected invalid location"); 1683 } 1684 } 1685 } 1686 } 1687 1688 @Override 1689 public boolean supportsPredictiveItemAnimations() { 1690 return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; 1691 } 1692 1693 /** 1694 * Helper class that keeps temporary state while {LayoutManager} is filling out the empty 1695 * space. 1696 */ 1697 static class LayoutState { 1698 1699 final static String TAG = "LinearLayoutManager#LayoutState"; 1700 1701 final static int LAYOUT_START = -1; 1702 1703 final static int LAYOUT_END = 1; 1704 1705 final static int INVALID_LAYOUT = Integer.MIN_VALUE; 1706 1707 final static int ITEM_DIRECTION_HEAD = -1; 1708 1709 final static int ITEM_DIRECTION_TAIL = 1; 1710 1711 final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE; 1712 1713 /** 1714 * We may not want to recycle children in some cases (e.g. layout) 1715 */ 1716 boolean mRecycle = true; 1717 1718 /** 1719 * Pixel offset where layout should start 1720 */ 1721 int mOffset; 1722 1723 /** 1724 * Number of pixels that we should fill, in the layout direction. 1725 */ 1726 int mAvailable; 1727 1728 /** 1729 * Current position on the adapter to get the next item. 1730 */ 1731 int mCurrentPosition; 1732 1733 /** 1734 * Defines the direction in which the data adapter is traversed. 1735 * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL} 1736 */ 1737 int mItemDirection; 1738 1739 /** 1740 * Defines the direction in which the layout is filled. 1741 * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END} 1742 */ 1743 int mLayoutDirection; 1744 1745 /** 1746 * Used when LayoutState is constructed in a scrolling state. 1747 * It should be set the amount of scrolling we can make without creating a new view. 1748 * Settings this is required for efficient view recycling. 1749 */ 1750 int mScrollingOffset; 1751 1752 /** 1753 * Used if you want to pre-layout items that are not yet visible. 1754 * The difference with {@link #mAvailable} is that, when recycling, distance laid out for 1755 * {@link #mExtra} is not considered to avoid recycling visible children. 1756 */ 1757 int mExtra = 0; 1758 1759 /** 1760 * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value 1761 * is set to true, we skip removed views since they should not be laid out in post layout 1762 * step. 1763 */ 1764 boolean mIsPreLayout = false; 1765 1766 /** 1767 * When LLM needs to layout particular views, it sets this list in which case, LayoutState 1768 * will only return views from this list and return null if it cannot find an item. 1769 */ 1770 List<RecyclerView.ViewHolder> mScrapList = null; 1771 1772 /** 1773 * @return true if there are more items in the data adapter 1774 */ 1775 boolean hasMore(RecyclerView.State state) { 1776 return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); 1777 } 1778 1779 /** 1780 * Gets the view for the next element that we should layout. 1781 * Also updates current item index to the next item, based on {@link #mItemDirection} 1782 * 1783 * @return The next element that we should layout. 1784 */ 1785 View next(RecyclerView.Recycler recycler) { 1786 if (mScrapList != null) { 1787 return nextFromLimitedList(); 1788 } 1789 final View view = recycler.getViewForPosition(mCurrentPosition); 1790 mCurrentPosition += mItemDirection; 1791 return view; 1792 } 1793 1794 /** 1795 * Returns next item from limited list. 1796 * <p> 1797 * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection 1798 * 1799 * @return View if an item in the current position or direction exists if not null. 1800 */ 1801 private View nextFromLimitedList() { 1802 int size = mScrapList.size(); 1803 RecyclerView.ViewHolder closest = null; 1804 int closestDistance = Integer.MAX_VALUE; 1805 for (int i = 0; i < size; i++) { 1806 RecyclerView.ViewHolder viewHolder = mScrapList.get(i); 1807 if (!mIsPreLayout && viewHolder.isRemoved()) { 1808 continue; 1809 } 1810 final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection; 1811 if (distance < 0) { 1812 continue; // item is not in current direction 1813 } 1814 if (distance < closestDistance) { 1815 closest = viewHolder; 1816 closestDistance = distance; 1817 if (distance == 0) { 1818 break; 1819 } 1820 } 1821 } 1822 if (DEBUG) { 1823 Log.d(TAG, "layout from scrap. found view:?" + (closest != null)); 1824 } 1825 if (closest != null) { 1826 mCurrentPosition = closest.getPosition() + mItemDirection; 1827 return closest.itemView; 1828 } 1829 return null; 1830 } 1831 1832 void log() { 1833 Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" + 1834 mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection); 1835 } 1836 } 1837 1838 static class SavedState implements Parcelable { 1839 1840 int mAnchorPosition; 1841 1842 int mAnchorOffset; 1843 1844 boolean mAnchorLayoutFromEnd; 1845 1846 public SavedState() { 1847 1848 } 1849 1850 SavedState(Parcel in) { 1851 mAnchorPosition = in.readInt(); 1852 mAnchorOffset = in.readInt(); 1853 mAnchorLayoutFromEnd = in.readInt() == 1; 1854 } 1855 1856 public SavedState(SavedState other) { 1857 mAnchorPosition = other.mAnchorPosition; 1858 mAnchorOffset = other.mAnchorOffset; 1859 mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; 1860 } 1861 1862 boolean hasValidAnchor() { 1863 return mAnchorPosition >= 0; 1864 } 1865 1866 void invalidateAnchor() { 1867 mAnchorPosition = NO_POSITION; 1868 } 1869 1870 @Override 1871 public int describeContents() { 1872 return 0; 1873 } 1874 1875 @Override 1876 public void writeToParcel(Parcel dest, int flags) { 1877 dest.writeInt(mAnchorPosition); 1878 dest.writeInt(mAnchorOffset); 1879 dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); 1880 } 1881 1882 public static final Parcelable.Creator<SavedState> CREATOR 1883 = new Parcelable.Creator<SavedState>() { 1884 @Override 1885 public SavedState createFromParcel(Parcel in) { 1886 return new SavedState(in); 1887 } 1888 1889 @Override 1890 public SavedState[] newArray(int size) { 1891 return new SavedState[size]; 1892 } 1893 }; 1894 } 1895 1896 /** 1897 * Simple data class to keep Anchor information 1898 */ 1899 class AnchorInfo { 1900 int mPosition; 1901 int mCoordinate; 1902 boolean mLayoutFromEnd; 1903 void reset() { 1904 mPosition = NO_POSITION; 1905 mCoordinate = INVALID_OFFSET; 1906 mLayoutFromEnd = false; 1907 } 1908 1909 /** 1910 * assigns anchor coordinate from the RecyclerView's padding depending on current 1911 * layoutFromEnd value 1912 */ 1913 void assignCoordinateFromPadding() { 1914 mCoordinate = mLayoutFromEnd 1915 ? mOrientationHelper.getEndAfterPadding() 1916 : mOrientationHelper.getStartAfterPadding(); 1917 } 1918 1919 @Override 1920 public String toString() { 1921 return "AnchorInfo{" + 1922 "mPosition=" + mPosition + 1923 ", mCoordinate=" + mCoordinate + 1924 ", mLayoutFromEnd=" + mLayoutFromEnd + 1925 '}'; 1926 } 1927 1928 /** 1929 * Assign anchor position information from the provided view if it is valid as a reference 1930 * child. 1931 */ 1932 public boolean assignFromViewIfValid(View child, RecyclerView.State state) { 1933 RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); 1934 if (!lp.isItemRemoved() && lp.getViewPosition() >= 0 1935 && lp.getViewPosition() < state.getItemCount()) { 1936 assignFromView(child); 1937 return true; 1938 } 1939 return false; 1940 } 1941 1942 public void assignFromView(View child) { 1943 if (mLayoutFromEnd) { 1944 mCoordinate = mOrientationHelper.getDecoratedEnd(child) + 1945 mOrientationHelper.getTotalSpaceChange(); 1946 } else { 1947 mCoordinate = mOrientationHelper.getDecoratedStart(child); 1948 } 1949 1950 mPosition = getPosition(child); 1951 } 1952 } 1953 1954 protected static class LayoutChunkResult { 1955 public int mConsumed; 1956 public boolean mFinished; 1957 public boolean mIgnoreConsumed; 1958 public boolean mFocusable; 1959 1960 void resetInternal() { 1961 mConsumed = 0; 1962 mFinished = false; 1963 mIgnoreConsumed = false; 1964 mFocusable = false; 1965 } 1966 } 1967} 1968