1 /* 2 * Copyright (C) 2013 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 18 package android.support.v7.widget; 19 20 import android.content.Context; 21 import android.database.Observable; 22 import android.graphics.Canvas; 23 import android.graphics.PointF; 24 import android.graphics.Rect; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.support.annotation.Nullable; 30 import android.support.v4.util.ArrayMap; 31 import android.support.v4.view.MotionEventCompat; 32 import android.support.v4.view.VelocityTrackerCompat; 33 import android.support.v4.view.ViewCompat; 34 import android.support.v4.view.ViewConfigurationCompat; 35 import android.support.v4.view.accessibility.AccessibilityEventCompat; 36 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 37 import android.support.v4.view.accessibility.AccessibilityRecordCompat; 38 import android.support.v4.widget.EdgeEffectCompat; 39 import android.support.v4.widget.ScrollerCompat; 40 import static android.support.v7.widget.AdapterHelper.UpdateOp; 41 import static android.support.v7.widget.AdapterHelper.Callback; 42 43 import android.util.AttributeSet; 44 import android.util.Log; 45 import android.util.SparseArray; 46 import android.util.SparseIntArray; 47 import android.view.FocusFinder; 48 import android.view.MotionEvent; 49 import android.view.VelocityTracker; 50 import android.view.View; 51 import android.view.ViewConfiguration; 52 import android.view.ViewGroup; 53 import android.view.ViewParent; 54 import android.view.accessibility.AccessibilityEvent; 55 import android.view.accessibility.AccessibilityManager; 56 import android.view.animation.Interpolator; 57 58 import java.util.ArrayList; 59 import java.util.Collections; 60 import java.util.List; 61 62 /** 63 * A flexible view for providing a limited window into a large data set. 64 * 65 * <h3>Glossary of terms:</h3> 66 * 67 * <ul> 68 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 69 * that represent items in a data set.</li> 70 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 71 * <li><em>Index:</em> The index of an attached child view as used in a call to 72 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 73 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 74 * to a <em>position</em> within the adapter.</li> 75 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 76 * position may be placed in a cache for later reuse to display the same type of data again 77 * later. This can drastically improve performance by skipping initial layout inflation 78 * or construction.</li> 79 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 80 * state during layout. Scrap views may be reused without becoming fully detached 81 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 82 * by the adapter if the view was considered <em>dirty</em>.</li> 83 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 84 * being displayed.</li> 85 * </ul> 86 * 87 * <h4>Positions in RecyclerView:</h4> 88 * <p> 89 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and 90 * {@link LayoutManager} to be able to detect data set changes in batches during a layout 91 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. 92 * It also helps with performance because all view bindings happen at the same time and unnecessary 93 * bindings are avoided. 94 * <p> 95 * For this reason, there are two types of <code>position</code> related methods in RecyclerView: 96 * <ul> 97 * <li>layout position: Position of an item in the latest layout calculation. This is the 98 * position from the LayoutManager's perspective.</li> 99 * <li>adapter position: Position of an item in the adapter. This is the position from 100 * the Adapter's perspective.</li> 101 * </ul> 102 * <p> 103 * These two positions are the same except the time between dispatching <code>adapter.notify* 104 * </code> events and calculating the updated layout. 105 * <p> 106 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest 107 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, 108 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the 109 * last layout calculation. You can rely on these positions to be consistent with what user is 110 * currently seeing on the screen. For example, if you have a list of items on the screen and user 111 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user 112 * is seeing. 113 * <p> 114 * The other set of position related methods are in the form of 115 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()}, 116 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to 117 * work with up-to-date adapter positions even if they may not have been reflected to layout yet. 118 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use 119 * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate 120 * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has 121 * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or 122 * <code>null</code> results from these methods. 123 * <p> 124 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when 125 * writing an {@link Adapter}, you probably want to use adapter positions. 126 */ 127 public class RecyclerView extends ViewGroup { 128 private static final String TAG = "RecyclerView"; 129 130 private static final boolean DEBUG = false; 131 132 /** 133 * On Kitkat, there is a bug which prevents DisplayList from being invalidated if a View is two 134 * levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by setting 135 * View's visibility to INVISIBLE when View is detached. On Kitkat, Recycler recursively 136 * traverses itemView and invalidates display list for each ViewGroup that matches this 137 * criteria. 138 */ 139 private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 19 || 140 Build.VERSION.SDK_INT == 20; 141 142 private static final boolean DISPATCH_TEMP_DETACH = false; 143 public static final int HORIZONTAL = 0; 144 public static final int VERTICAL = 1; 145 146 public static final int NO_POSITION = -1; 147 public static final long NO_ID = -1; 148 public static final int INVALID_TYPE = -1; 149 150 /** 151 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 152 * that the RecyclerView should use the standard touch slop for smooth, 153 * continuous scrolling. 154 */ 155 public static final int TOUCH_SLOP_DEFAULT = 0; 156 157 /** 158 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 159 * that the RecyclerView should use the standard touch slop for scrolling 160 * widgets that snap to a page or other coarse-grained barrier. 161 */ 162 public static final int TOUCH_SLOP_PAGING = 1; 163 164 private static final int MAX_SCROLL_DURATION = 2000; 165 166 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 167 168 final Recycler mRecycler = new Recycler(); 169 170 private SavedState mPendingSavedState; 171 172 AdapterHelper mAdapterHelper; 173 174 ChildHelper mChildHelper; 175 176 // we use this like a set 177 final List<View> mDisappearingViewsInLayoutPass = new ArrayList<View>(); 178 179 /** 180 * Prior to L, there is no way to query this variable which is why we override the setter and 181 * track it here. 182 */ 183 private boolean mClipToPadding; 184 185 /** 186 * Note: this Runnable is only ever posted if: 187 * 1) We've been through first layout 188 * 2) We know we have a fixed size (mHasFixedSize) 189 * 3) We're attached 190 */ 191 private final Runnable mUpdateChildViewsRunnable = new Runnable() { 192 public void run() { 193 if (!mFirstLayoutComplete) { 194 // a layout request will happen, we should not do layout here. 195 return; 196 } 197 if (mDataSetHasChangedAfterLayout) { 198 dispatchLayout(); 199 } else if (mAdapterHelper.hasPendingUpdates()) { 200 eatRequestLayout(); 201 mAdapterHelper.preProcess(); 202 if (!mLayoutRequestEaten) { 203 // We run this after pre-processing is complete so that ViewHolders have their 204 // final adapter positions. No need to run it if a layout is already requested. 205 rebindUpdatedViewHolders(); 206 } 207 resumeRequestLayout(true); 208 } 209 } 210 }; 211 212 private final Rect mTempRect = new Rect(); 213 private Adapter mAdapter; 214 private LayoutManager mLayout; 215 private RecyclerListener mRecyclerListener; 216 private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>(); 217 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 218 new ArrayList<OnItemTouchListener>(); 219 private OnItemTouchListener mActiveOnItemTouchListener; 220 private boolean mIsAttached; 221 private boolean mHasFixedSize; 222 private boolean mFirstLayoutComplete; 223 private boolean mEatRequestLayout; 224 private boolean mLayoutRequestEaten; 225 private boolean mAdapterUpdateDuringMeasure; 226 private final boolean mPostUpdatesOnAnimation; 227 private final AccessibilityManager mAccessibilityManager; 228 229 /** 230 * Set to true when an adapter data set changed notification is received. 231 * In that case, we cannot run any animations since we don't know what happened. 232 */ 233 private boolean mDataSetHasChangedAfterLayout = false; 234 235 /** 236 * This variable is set to true during a dispatchLayout and/or scroll. 237 * Some methods should not be called during these periods (e.g. adapter data change). 238 * Doing so will create hard to find bugs so we better check it and throw an exception. 239 * 240 * @see #assertInLayoutOrScroll(String) 241 * @see #assertNotInLayoutOrScroll(String) 242 */ 243 private boolean mRunningLayoutOrScroll = false; 244 245 private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 246 247 ItemAnimator mItemAnimator = new DefaultItemAnimator(); 248 249 private static final int INVALID_POINTER = -1; 250 251 /** 252 * The RecyclerView is not currently scrolling. 253 * @see #getScrollState() 254 */ 255 public static final int SCROLL_STATE_IDLE = 0; 256 257 /** 258 * The RecyclerView is currently being dragged by outside input such as user touch input. 259 * @see #getScrollState() 260 */ 261 public static final int SCROLL_STATE_DRAGGING = 1; 262 263 /** 264 * The RecyclerView is currently animating to a final position while not under 265 * outside control. 266 * @see #getScrollState() 267 */ 268 public static final int SCROLL_STATE_SETTLING = 2; 269 270 // Touch/scrolling handling 271 272 private int mScrollState = SCROLL_STATE_IDLE; 273 private int mScrollPointerId = INVALID_POINTER; 274 private VelocityTracker mVelocityTracker; 275 private int mInitialTouchX; 276 private int mInitialTouchY; 277 private int mLastTouchX; 278 private int mLastTouchY; 279 private int mTouchSlop; 280 private final int mMinFlingVelocity; 281 private final int mMaxFlingVelocity; 282 283 private final ViewFlinger mViewFlinger = new ViewFlinger(); 284 285 final State mState = new State(); 286 287 private OnScrollListener mScrollListener; 288 289 // For use in item animations 290 boolean mItemsAddedOrRemoved = false; 291 boolean mItemsChanged = false; 292 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = 293 new ItemAnimatorRestoreListener(); 294 private boolean mPostedAnimatorRunner = false; 295 private RecyclerViewAccessibilityDelegate mAccessibilityDelegate; 296 297 // simple array to keep min and max child position during a layout calculation 298 // preserved not to create a new one in each layout pass 299 private final int[] mMinMaxLayoutPositions = new int[2]; 300 301 private Runnable mItemAnimatorRunner = new Runnable() { 302 @Override 303 public void run() { 304 if (mItemAnimator != null) { 305 mItemAnimator.runPendingAnimations(); 306 } 307 mPostedAnimatorRunner = false; 308 } 309 }; 310 311 private static final Interpolator sQuinticInterpolator = new Interpolator() { 312 public float getInterpolation(float t) { 313 t -= 1.0f; 314 return t * t * t * t * t + 1.0f; 315 } 316 }; 317 RecyclerView(Context context)318 public RecyclerView(Context context) { 319 this(context, null); 320 } 321 RecyclerView(Context context, AttributeSet attrs)322 public RecyclerView(Context context, AttributeSet attrs) { 323 this(context, attrs, 0); 324 } 325 RecyclerView(Context context, AttributeSet attrs, int defStyle)326 public RecyclerView(Context context, AttributeSet attrs, int defStyle) { 327 super(context, attrs, defStyle); 328 329 final int version = Build.VERSION.SDK_INT; 330 mPostUpdatesOnAnimation = version >= 16; 331 332 final ViewConfiguration vc = ViewConfiguration.get(context); 333 mTouchSlop = vc.getScaledTouchSlop(); 334 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 335 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 336 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 337 338 mItemAnimator.setListener(mItemAnimatorListener); 339 initAdapterManager(); 340 initChildrenHelper(); 341 // If not explicitly specified this view is important for accessibility. 342 if (ViewCompat.getImportantForAccessibility(this) 343 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 344 ViewCompat.setImportantForAccessibility(this, 345 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 346 } 347 mAccessibilityManager = (AccessibilityManager) getContext() 348 .getSystemService(Context.ACCESSIBILITY_SERVICE); 349 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 350 } 351 352 /** 353 * Returns the accessibility delegate compatibility implementation used by the RecyclerView. 354 * @return An instance of AccessibilityDelegateCompat used by RecyclerView 355 */ getCompatAccessibilityDelegate()356 public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { 357 return mAccessibilityDelegate; 358 } 359 360 /** 361 * Sets the accessibility delegate compatibility implementation used by RecyclerView. 362 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. 363 */ setAccessibilityDelegateCompat( RecyclerViewAccessibilityDelegate accessibilityDelegate)364 public void setAccessibilityDelegateCompat( 365 RecyclerViewAccessibilityDelegate accessibilityDelegate) { 366 mAccessibilityDelegate = accessibilityDelegate; 367 ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); 368 } 369 initChildrenHelper()370 private void initChildrenHelper() { 371 mChildHelper = new ChildHelper(new ChildHelper.Callback() { 372 @Override 373 public int getChildCount() { 374 return RecyclerView.this.getChildCount(); 375 } 376 377 @Override 378 public void addView(View child, int index) { 379 RecyclerView.this.addView(child, index); 380 dispatchChildAttached(child); 381 } 382 383 @Override 384 public int indexOfChild(View view) { 385 return RecyclerView.this.indexOfChild(view); 386 } 387 388 @Override 389 public void removeViewAt(int index) { 390 final View child = RecyclerView.this.getChildAt(index); 391 if (child != null) { 392 dispatchChildDetached(child); 393 } 394 RecyclerView.this.removeViewAt(index); 395 } 396 397 @Override 398 public View getChildAt(int offset) { 399 return RecyclerView.this.getChildAt(offset); 400 } 401 402 @Override 403 public void removeAllViews() { 404 final int count = getChildCount(); 405 for (int i = 0; i < count; i ++) { 406 dispatchChildDetached(getChildAt(i)); 407 } 408 RecyclerView.this.removeAllViews(); 409 } 410 411 @Override 412 public ViewHolder getChildViewHolder(View view) { 413 return getChildViewHolderInt(view); 414 } 415 416 @Override 417 public void attachViewToParent(View child, int index, 418 ViewGroup.LayoutParams layoutParams) { 419 final ViewHolder vh = getChildViewHolderInt(child); 420 if (vh != null) { 421 if (!vh.isTmpDetached() && !vh.shouldIgnore()) { 422 throw new IllegalArgumentException("Called attach on a child which is not" 423 + " detached: " + vh); 424 } 425 if (DEBUG) { 426 Log.d(TAG, "reAttach " + vh); 427 } 428 vh.clearTmpDetachFlag(); 429 } 430 RecyclerView.this.attachViewToParent(child, index, layoutParams); 431 } 432 433 @Override 434 public void detachViewFromParent(int offset) { 435 final View view = getChildAt(offset); 436 if (view != null) { 437 final ViewHolder vh = getChildViewHolderInt(view); 438 if (vh != null) { 439 if (vh.isTmpDetached() && !vh.shouldIgnore()) { 440 throw new IllegalArgumentException("called detach on an already" 441 + " detached child " + vh); 442 } 443 if (DEBUG) { 444 Log.d(TAG, "tmpDetach " + vh); 445 } 446 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); 447 } 448 } 449 RecyclerView.this.detachViewFromParent(offset); 450 } 451 }); 452 } 453 initAdapterManager()454 void initAdapterManager() { 455 mAdapterHelper = new AdapterHelper(new Callback() { 456 @Override 457 public ViewHolder findViewHolder(int position) { 458 return findViewHolderForPosition(position, true); 459 } 460 461 @Override 462 public void offsetPositionsForRemovingInvisible(int start, int count) { 463 offsetPositionRecordsForRemove(start, count, true); 464 mItemsAddedOrRemoved = true; 465 mState.mDeletedInvisibleItemCountSincePreviousLayout += count; 466 } 467 468 @Override 469 public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount) { 470 offsetPositionRecordsForRemove(positionStart, itemCount, false); 471 mItemsAddedOrRemoved = true; 472 } 473 474 @Override 475 public void markViewHoldersUpdated(int positionStart, int itemCount) { 476 viewRangeUpdate(positionStart, itemCount); 477 mItemsChanged = true; 478 } 479 480 @Override 481 public void onDispatchFirstPass(UpdateOp op) { 482 dispatchUpdate(op); 483 } 484 485 void dispatchUpdate(UpdateOp op) { 486 switch (op.cmd) { 487 case UpdateOp.ADD: 488 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); 489 break; 490 case UpdateOp.REMOVE: 491 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); 492 break; 493 case UpdateOp.UPDATE: 494 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount); 495 break; 496 case UpdateOp.MOVE: 497 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); 498 break; 499 } 500 } 501 502 @Override 503 public void onDispatchSecondPass(UpdateOp op) { 504 dispatchUpdate(op); 505 } 506 507 @Override 508 public void offsetPositionsForAdd(int positionStart, int itemCount) { 509 offsetPositionRecordsForInsert(positionStart, itemCount); 510 mItemsAddedOrRemoved = true; 511 } 512 513 @Override 514 public void offsetPositionsForMove(int from, int to) { 515 offsetPositionRecordsForMove(from, to); 516 // should we create mItemsMoved ? 517 mItemsAddedOrRemoved = true; 518 } 519 }); 520 } 521 522 /** 523 * RecyclerView can perform several optimizations if it can know in advance that changes in 524 * adapter content cannot change the size of the RecyclerView itself. 525 * If your use of RecyclerView falls into this category, set this to true. 526 * 527 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 528 */ setHasFixedSize(boolean hasFixedSize)529 public void setHasFixedSize(boolean hasFixedSize) { 530 mHasFixedSize = hasFixedSize; 531 } 532 533 /** 534 * @return true if the app has specified that changes in adapter content cannot change 535 * the size of the RecyclerView itself. 536 */ hasFixedSize()537 public boolean hasFixedSize() { 538 return mHasFixedSize; 539 } 540 541 @Override setClipToPadding(boolean clipToPadding)542 public void setClipToPadding(boolean clipToPadding) { 543 if (clipToPadding != mClipToPadding) { 544 invalidateGlows(); 545 } 546 mClipToPadding = clipToPadding; 547 super.setClipToPadding(clipToPadding); 548 if (mFirstLayoutComplete) { 549 requestLayout(); 550 } 551 } 552 553 /** 554 * Configure the scrolling touch slop for a specific use case. 555 * 556 * Set up the RecyclerView's scrolling motion threshold based on common usages. 557 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. 558 * 559 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing 560 * the intended usage of this RecyclerView 561 */ setScrollingTouchSlop(int slopConstant)562 public void setScrollingTouchSlop(int slopConstant) { 563 final ViewConfiguration vc = ViewConfiguration.get(getContext()); 564 switch (slopConstant) { 565 default: 566 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " 567 + slopConstant + "; using default value"); 568 // fall-through 569 case TOUCH_SLOP_DEFAULT: 570 mTouchSlop = vc.getScaledTouchSlop(); 571 break; 572 573 case TOUCH_SLOP_PAGING: 574 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc); 575 break; 576 } 577 } 578 579 /** 580 * Swaps the current adapter with the provided one. It is similar to 581 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same 582 * {@link ViewHolder} and does not clear the RecycledViewPool. 583 * <p> 584 * Note that it still calls onAdapterChanged callbacks. 585 * 586 * @param adapter The new adapter to set, or null to set no adapter. 587 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing 588 * Views. If adapters have stable ids and/or you want to 589 * animate the disappearing views, you may prefer to set 590 * this to false. 591 * @see #setAdapter(Adapter) 592 */ swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews)593 public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) { 594 setAdapterInternal(adapter, true, removeAndRecycleExistingViews); 595 setDataSetChangedAfterLayout(); 596 requestLayout(); 597 } 598 /** 599 * Set a new adapter to provide child views on demand. 600 * <p> 601 * When adapter is changed, all existing views are recycled back to the pool. If the pool has 602 * only one adapter, it will be cleared. 603 * 604 * @param adapter The new adapter to set, or null to set no adapter. 605 * @see #swapAdapter(Adapter, boolean) 606 */ setAdapter(Adapter adapter)607 public void setAdapter(Adapter adapter) { 608 setAdapterInternal(adapter, false, true); 609 requestLayout(); 610 } 611 612 /** 613 * Replaces the current adapter with the new one and triggers listeners. 614 * @param adapter The new adapter 615 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and 616 * item types with the current adapter (helps us avoid cache 617 * invalidation). 618 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If 619 * compatibleWithPrevious is false, this parameter is ignored. 620 */ setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews)621 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, 622 boolean removeAndRecycleViews) { 623 if (mAdapter != null) { 624 mAdapter.unregisterAdapterDataObserver(mObserver); 625 mAdapter.onDetachedFromRecyclerView(this); 626 } 627 if (!compatibleWithPrevious || removeAndRecycleViews) { 628 // end all running animations 629 if (mItemAnimator != null) { 630 mItemAnimator.endAnimations(); 631 } 632 // Since animations are ended, mLayout.children should be equal to 633 // recyclerView.children. This may not be true if item animator's end does not work as 634 // expected. (e.g. not release children instantly). It is safer to use mLayout's child 635 // count. 636 if (mLayout != null) { 637 mLayout.removeAndRecycleAllViews(mRecycler); 638 mLayout.removeAndRecycleScrapInt(mRecycler); 639 } 640 // we should clear it here before adapters are swapped to ensure correct callbacks. 641 mRecycler.clear(); 642 } 643 mAdapterHelper.reset(); 644 final Adapter oldAdapter = mAdapter; 645 mAdapter = adapter; 646 if (adapter != null) { 647 adapter.registerAdapterDataObserver(mObserver); 648 adapter.onAttachedToRecyclerView(this); 649 } 650 if (mLayout != null) { 651 mLayout.onAdapterChanged(oldAdapter, mAdapter); 652 } 653 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 654 mState.mStructureChanged = true; 655 markKnownViewsInvalid(); 656 } 657 658 /** 659 * Retrieves the previously set adapter or null if no adapter is set. 660 * 661 * @return The previously set adapter 662 * @see #setAdapter(Adapter) 663 */ getAdapter()664 public Adapter getAdapter() { 665 return mAdapter; 666 } 667 668 /** 669 * Register a listener that will be notified whenever a child view is recycled. 670 * 671 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 672 * that a child view is no longer needed. If an application associates expensive 673 * or heavyweight data with item views, this may be a good place to release 674 * or free those resources.</p> 675 * 676 * @param listener Listener to register, or null to clear 677 */ setRecyclerListener(RecyclerListener listener)678 public void setRecyclerListener(RecyclerListener listener) { 679 mRecyclerListener = listener; 680 } 681 682 /** 683 * Set the {@link LayoutManager} that this RecyclerView will use. 684 * 685 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 686 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 687 * layout arrangements for child views. These arrangements are controlled by the 688 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 689 * 690 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 691 * 692 * @param layout LayoutManager to use 693 */ setLayoutManager(LayoutManager layout)694 public void setLayoutManager(LayoutManager layout) { 695 if (layout == mLayout) { 696 return; 697 } 698 // TODO We should do this switch a dispachLayout pass and animate children. There is a good 699 // chance that LayoutManagers will re-use views. 700 if (mLayout != null) { 701 if (mIsAttached) { 702 mLayout.onDetachedFromWindow(this, mRecycler); 703 } 704 mLayout.setRecyclerView(null); 705 } 706 mRecycler.clear(); 707 mChildHelper.removeAllViewsUnfiltered(); 708 mLayout = layout; 709 if (layout != null) { 710 if (layout.mRecyclerView != null) { 711 throw new IllegalArgumentException("LayoutManager " + layout + 712 " is already attached to a RecyclerView: " + layout.mRecyclerView); 713 } 714 mLayout.setRecyclerView(this); 715 if (mIsAttached) { 716 mLayout.onAttachedToWindow(this); 717 } 718 } 719 requestLayout(); 720 } 721 722 @Override onSaveInstanceState()723 protected Parcelable onSaveInstanceState() { 724 SavedState state = new SavedState(super.onSaveInstanceState()); 725 if (mPendingSavedState != null) { 726 state.copyFrom(mPendingSavedState); 727 } else if (mLayout != null) { 728 state.mLayoutState = mLayout.onSaveInstanceState(); 729 } else { 730 state.mLayoutState = null; 731 } 732 733 return state; 734 } 735 736 @Override onRestoreInstanceState(Parcelable state)737 protected void onRestoreInstanceState(Parcelable state) { 738 mPendingSavedState = (SavedState) state; 739 super.onRestoreInstanceState(mPendingSavedState.getSuperState()); 740 if (mLayout != null && mPendingSavedState.mLayoutState != null) { 741 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); 742 } 743 } 744 745 /** 746 * Adds a view to the animatingViews list. 747 * mAnimatingViews holds the child views that are currently being kept around 748 * purely for the purpose of being animated out of view. They are drawn as a regular 749 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager 750 * as they are managed separately from the regular child views. 751 * @param viewHolder The ViewHolder to be removed 752 */ addAnimatingView(ViewHolder viewHolder)753 private void addAnimatingView(ViewHolder viewHolder) { 754 final View view = viewHolder.itemView; 755 final boolean alreadyParented = view.getParent() == this; 756 mRecycler.unscrapView(getChildViewHolder(view)); 757 if (viewHolder.isTmpDetached()) { 758 // re-attach 759 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); 760 } else if(!alreadyParented) { 761 mChildHelper.addView(view, true); 762 } else { 763 mChildHelper.hide(view); 764 } 765 } 766 767 /** 768 * Removes a view from the animatingViews list. 769 * @param view The view to be removed 770 * @see #addAnimatingView(RecyclerView.ViewHolder) 771 * @return true if an animating view is removed 772 */ removeAnimatingView(View view)773 private boolean removeAnimatingView(View view) { 774 eatRequestLayout(); 775 final boolean removed = mChildHelper.removeViewIfHidden(view); 776 if (removed) { 777 final ViewHolder viewHolder = getChildViewHolderInt(view); 778 mRecycler.unscrapView(viewHolder); 779 mRecycler.recycleViewHolderInternal(viewHolder); 780 if (DEBUG) { 781 Log.d(TAG, "after removing animated view: " + view + ", " + this); 782 } 783 } 784 resumeRequestLayout(false); 785 return removed; 786 } 787 788 /** 789 * Return the {@link LayoutManager} currently responsible for 790 * layout policy for this RecyclerView. 791 * 792 * @return The currently bound LayoutManager 793 */ getLayoutManager()794 public LayoutManager getLayoutManager() { 795 return mLayout; 796 } 797 798 /** 799 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 800 * if no pool is set for this view a new one will be created. See 801 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 802 * 803 * @return The pool used to store recycled item views for reuse. 804 * @see #setRecycledViewPool(RecycledViewPool) 805 */ getRecycledViewPool()806 public RecycledViewPool getRecycledViewPool() { 807 return mRecycler.getRecycledViewPool(); 808 } 809 810 /** 811 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 812 * This can be useful if you have multiple RecyclerViews with adapters that use the same 813 * view types, for example if you have several data sets with the same kinds of item views 814 * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. 815 * 816 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 817 */ setRecycledViewPool(RecycledViewPool pool)818 public void setRecycledViewPool(RecycledViewPool pool) { 819 mRecycler.setRecycledViewPool(pool); 820 } 821 822 /** 823 * Sets a new {@link ViewCacheExtension} to be used by the Recycler. 824 * 825 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. 826 * 827 * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)} 828 */ setViewCacheExtension(ViewCacheExtension extension)829 public void setViewCacheExtension(ViewCacheExtension extension) { 830 mRecycler.setViewCacheExtension(extension); 831 } 832 833 /** 834 * Set the number of offscreen views to retain before adding them to the potentially shared 835 * {@link #getRecycledViewPool() recycled view pool}. 836 * 837 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 838 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 839 * to rebind them.</p> 840 * 841 * @param size Number of views to cache offscreen before returning them to the general 842 * recycled view pool 843 */ setItemViewCacheSize(int size)844 public void setItemViewCacheSize(int size) { 845 mRecycler.setViewCacheSize(size); 846 } 847 848 /** 849 * Return the current scrolling state of the RecyclerView. 850 * 851 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 852 * {@link #SCROLL_STATE_SETTLING} 853 */ getScrollState()854 public int getScrollState() { 855 return mScrollState; 856 } 857 setScrollState(int state)858 private void setScrollState(int state) { 859 if (state == mScrollState) { 860 return; 861 } 862 if (DEBUG) { 863 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, new Exception()); 864 } 865 mScrollState = state; 866 if (state != SCROLL_STATE_SETTLING) { 867 stopScrollersInternal(); 868 } 869 if (mScrollListener != null) { 870 mScrollListener.onScrollStateChanged(this, state); 871 } 872 if (mLayout != null) { 873 mLayout.onScrollStateChanged(state); 874 } 875 } 876 877 /** 878 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 879 * affect both measurement and drawing of individual item views. 880 * 881 * <p>Item decorations are ordered. Decorations placed earlier in the list will 882 * be run/queried/drawn first for their effects on item views. Padding added to views 883 * will be nested; a padding added by an earlier decoration will mean further 884 * item decorations in the list will be asked to draw/pad within the previous decoration's 885 * given area.</p> 886 * 887 * @param decor Decoration to add 888 * @param index Position in the decoration chain to insert this decoration at. If this value 889 * is negative the decoration will be added at the end. 890 */ addItemDecoration(ItemDecoration decor, int index)891 public void addItemDecoration(ItemDecoration decor, int index) { 892 if (mLayout != null) { 893 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" 894 + " layout"); 895 } 896 if (mItemDecorations.isEmpty()) { 897 setWillNotDraw(false); 898 } 899 if (index < 0) { 900 mItemDecorations.add(decor); 901 } else { 902 mItemDecorations.add(index, decor); 903 } 904 markItemDecorInsetsDirty(); 905 requestLayout(); 906 } 907 908 /** 909 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 910 * affect both measurement and drawing of individual item views. 911 * 912 * <p>Item decorations are ordered. Decorations placed earlier in the list will 913 * be run/queried/drawn first for their effects on item views. Padding added to views 914 * will be nested; a padding added by an earlier decoration will mean further 915 * item decorations in the list will be asked to draw/pad within the previous decoration's 916 * given area.</p> 917 * 918 * @param decor Decoration to add 919 */ addItemDecoration(ItemDecoration decor)920 public void addItemDecoration(ItemDecoration decor) { 921 addItemDecoration(decor, -1); 922 } 923 924 /** 925 * Remove an {@link ItemDecoration} from this RecyclerView. 926 * 927 * <p>The given decoration will no longer impact the measurement and drawing of 928 * item views.</p> 929 * 930 * @param decor Decoration to remove 931 * @see #addItemDecoration(ItemDecoration) 932 */ removeItemDecoration(ItemDecoration decor)933 public void removeItemDecoration(ItemDecoration decor) { 934 if (mLayout != null) { 935 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" 936 + " layout"); 937 } 938 mItemDecorations.remove(decor); 939 if (mItemDecorations.isEmpty()) { 940 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 941 } 942 markItemDecorInsetsDirty(); 943 requestLayout(); 944 } 945 946 /** 947 * Set a listener that will be notified of any changes in scroll state or position. 948 * 949 * @param listener Listener to set or null to clear 950 */ setOnScrollListener(OnScrollListener listener)951 public void setOnScrollListener(OnScrollListener listener) { 952 mScrollListener = listener; 953 } 954 955 /** 956 * Convenience method to scroll to a certain position. 957 * 958 * RecyclerView does not implement scrolling logic, rather forwards the call to 959 * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)} 960 * @param position Scroll to this adapter position 961 * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int) 962 */ scrollToPosition(int position)963 public void scrollToPosition(int position) { 964 stopScroll(); 965 if (mLayout == null) { 966 Log.e(TAG, "Cannot scroll to position a LayoutManager set. " + 967 "Call setLayoutManager with a non-null argument."); 968 return; 969 } 970 mLayout.scrollToPosition(position); 971 awakenScrollBars(); 972 } 973 974 /** 975 * Starts a smooth scroll to an adapter position. 976 * <p> 977 * To support smooth scrolling, you must override 978 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a 979 * {@link SmoothScroller}. 980 * <p> 981 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to 982 * provide a custom smooth scroll logic, override 983 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your 984 * LayoutManager. 985 * 986 * @param position The adapter position to scroll to 987 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) 988 */ smoothScrollToPosition(int position)989 public void smoothScrollToPosition(int position) { 990 if (mLayout == null) { 991 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + 992 "Call setLayoutManager with a non-null argument."); 993 return; 994 } 995 mLayout.smoothScrollToPosition(this, mState, position); 996 } 997 998 @Override scrollTo(int x, int y)999 public void scrollTo(int x, int y) { 1000 throw new UnsupportedOperationException( 1001 "RecyclerView does not support scrolling to an absolute position."); 1002 } 1003 1004 @Override scrollBy(int x, int y)1005 public void scrollBy(int x, int y) { 1006 if (mLayout == null) { 1007 Log.e(TAG, "Cannot scroll without a LayoutManager set. " + 1008 "Call setLayoutManager with a non-null argument."); 1009 return; 1010 } 1011 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1012 final boolean canScrollVertical = mLayout.canScrollVertically(); 1013 if (canScrollHorizontal || canScrollVertical) { 1014 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0); 1015 } 1016 } 1017 1018 /** 1019 * Helper method reflect data changes to the state. 1020 * <p> 1021 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change 1022 * but data actually changed. 1023 * <p> 1024 * This method consumes all deferred changes to avoid that case. 1025 */ consumePendingUpdateOperations()1026 private void consumePendingUpdateOperations() { 1027 mUpdateChildViewsRunnable.run(); 1028 } 1029 1030 /** 1031 * Does not perform bounds checking. Used by internal methods that have already validated input. 1032 * 1033 * @return Whether any scroll was consumed in either direction. 1034 */ scrollByInternal(int x, int y)1035 boolean scrollByInternal(int x, int y) { 1036 int overscrollX = 0, overscrollY = 0; 1037 int hresult = 0, vresult = 0; 1038 consumePendingUpdateOperations(); 1039 if (mAdapter != null) { 1040 eatRequestLayout(); 1041 mRunningLayoutOrScroll = true; 1042 if (x != 0) { 1043 hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); 1044 overscrollX = x - hresult; 1045 } 1046 if (y != 0) { 1047 vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); 1048 overscrollY = y - vresult; 1049 } 1050 if (supportsChangeAnimations()) { 1051 // Fix up shadow views used by changing animations 1052 int count = mChildHelper.getChildCount(); 1053 for (int i = 0; i < count; i++) { 1054 View view = mChildHelper.getChildAt(i); 1055 ViewHolder holder = getChildViewHolder(view); 1056 if (holder != null && holder.mShadowingHolder != null) { 1057 ViewHolder shadowingHolder = holder.mShadowingHolder; 1058 View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null; 1059 if (shadowingView != null) { 1060 int left = view.getLeft(); 1061 int top = view.getTop(); 1062 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { 1063 shadowingView.layout(left, top, 1064 left + shadowingView.getWidth(), 1065 top + shadowingView.getHeight()); 1066 } 1067 } 1068 } 1069 } 1070 } 1071 mRunningLayoutOrScroll = false; 1072 resumeRequestLayout(false); 1073 } 1074 if (!mItemDecorations.isEmpty()) { 1075 invalidate(); 1076 } 1077 if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { 1078 considerReleasingGlowsOnScroll(x, y); 1079 pullGlows(overscrollX, overscrollY); 1080 } 1081 if (hresult != 0 || vresult != 0) { 1082 notifyOnScrolled(hresult, vresult); 1083 } 1084 if (!awakenScrollBars()) { 1085 invalidate(); 1086 } 1087 return hresult != 0 || vresult != 0; 1088 } 1089 1090 /** 1091 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal 1092 * range. This value is used to compute the length of the thumb within the scrollbar's track. 1093 * </p> 1094 * 1095 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1096 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> 1097 * 1098 * <p>Default implementation returns 0.</p> 1099 * 1100 * <p>If you want to support scroll bars, override 1101 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your 1102 * LayoutManager. </p> 1103 * 1104 * @return The horizontal offset of the scrollbar's thumb 1105 * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset 1106 * (RecyclerView.Adapter) 1107 */ 1108 @Override computeHorizontalScrollOffset()1109 protected int computeHorizontalScrollOffset() { 1110 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) 1111 : 0; 1112 } 1113 1114 /** 1115 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the 1116 * horizontal range. This value is used to compute the length of the thumb within the 1117 * scrollbar's track.</p> 1118 * 1119 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1120 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> 1121 * 1122 * <p>Default implementation returns 0.</p> 1123 * 1124 * <p>If you want to support scroll bars, override 1125 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your 1126 * LayoutManager.</p> 1127 * 1128 * @return The horizontal extent of the scrollbar's thumb 1129 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) 1130 */ 1131 @Override computeHorizontalScrollExtent()1132 protected int computeHorizontalScrollExtent() { 1133 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; 1134 } 1135 1136 /** 1137 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> 1138 * 1139 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1140 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> 1141 * 1142 * <p>Default implementation returns 0.</p> 1143 * 1144 * <p>If you want to support scroll bars, override 1145 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your 1146 * LayoutManager.</p> 1147 * 1148 * @return The total horizontal range represented by the vertical scrollbar 1149 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) 1150 */ 1151 @Override computeHorizontalScrollRange()1152 protected int computeHorizontalScrollRange() { 1153 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; 1154 } 1155 1156 /** 1157 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. 1158 * This value is used to compute the length of the thumb within the scrollbar's track. </p> 1159 * 1160 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1161 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> 1162 * 1163 * <p>Default implementation returns 0.</p> 1164 * 1165 * <p>If you want to support scroll bars, override 1166 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your 1167 * LayoutManager.</p> 1168 * 1169 * @return The vertical offset of the scrollbar's thumb 1170 * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset 1171 * (RecyclerView.Adapter) 1172 */ 1173 @Override computeVerticalScrollOffset()1174 protected int computeVerticalScrollOffset() { 1175 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; 1176 } 1177 1178 /** 1179 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. 1180 * This value is used to compute the length of the thumb within the scrollbar's track.</p> 1181 * 1182 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1183 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> 1184 * 1185 * <p>Default implementation returns 0.</p> 1186 * 1187 * <p>If you want to support scroll bars, override 1188 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your 1189 * LayoutManager.</p> 1190 * 1191 * @return The vertical extent of the scrollbar's thumb 1192 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) 1193 */ 1194 @Override computeVerticalScrollExtent()1195 protected int computeVerticalScrollExtent() { 1196 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; 1197 } 1198 1199 /** 1200 * <p>Compute the vertical range that the vertical scrollbar represents.</p> 1201 * 1202 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1203 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> 1204 * 1205 * <p>Default implementation returns 0.</p> 1206 * 1207 * <p>If you want to support scroll bars, override 1208 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your 1209 * LayoutManager.</p> 1210 * 1211 * @return The total vertical range represented by the vertical scrollbar 1212 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) 1213 */ 1214 @Override computeVerticalScrollRange()1215 protected int computeVerticalScrollRange() { 1216 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; 1217 } 1218 1219 eatRequestLayout()1220 void eatRequestLayout() { 1221 if (!mEatRequestLayout) { 1222 mEatRequestLayout = true; 1223 mLayoutRequestEaten = false; 1224 } 1225 } 1226 resumeRequestLayout(boolean performLayoutChildren)1227 void resumeRequestLayout(boolean performLayoutChildren) { 1228 if (mEatRequestLayout) { 1229 if (performLayoutChildren && mLayoutRequestEaten && 1230 mLayout != null && mAdapter != null) { 1231 dispatchLayout(); 1232 } 1233 mEatRequestLayout = false; 1234 mLayoutRequestEaten = false; 1235 } 1236 } 1237 1238 /** 1239 * Animate a scroll by the given amount of pixels along either axis. 1240 * 1241 * @param dx Pixels to scroll horizontally 1242 * @param dy Pixels to scroll vertically 1243 */ smoothScrollBy(int dx, int dy)1244 public void smoothScrollBy(int dx, int dy) { 1245 if (mLayout == null) { 1246 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + 1247 "Call setLayoutManager with a non-null argument."); 1248 return; 1249 } 1250 if (!mLayout.canScrollHorizontally()) { 1251 dx = 0; 1252 } 1253 if (!mLayout.canScrollVertically()) { 1254 dy = 0; 1255 } 1256 if (dx != 0 || dy != 0) { 1257 mViewFlinger.smoothScrollBy(dx, dy); 1258 } 1259 } 1260 1261 /** 1262 * Begin a standard fling with an initial velocity along each axis in pixels per second. 1263 * If the velocity given is below the system-defined minimum this method will return false 1264 * and no fling will occur. 1265 * 1266 * @param velocityX Initial horizontal velocity in pixels per second 1267 * @param velocityY Initial vertical velocity in pixels per second 1268 * @return true if the fling was started, false if the velocity was too low to fling or 1269 * LayoutManager does not support scrolling in the axis fling is issued. 1270 * 1271 * @see LayoutManager#canScrollVertically() 1272 * @see LayoutManager#canScrollHorizontally() 1273 */ fling(int velocityX, int velocityY)1274 public boolean fling(int velocityX, int velocityY) { 1275 if (mLayout == null) { 1276 Log.e(TAG, "Cannot fling without a LayoutManager set. " + 1277 "Call setLayoutManager with a non-null argument."); 1278 return false; 1279 } 1280 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1281 final boolean canScrollVertical = mLayout.canScrollVertically(); 1282 if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { 1283 velocityX = 0; 1284 } 1285 if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { 1286 velocityY = 0; 1287 } 1288 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 1289 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 1290 if (velocityX != 0 || velocityY != 0) { 1291 mViewFlinger.fling(velocityX, velocityY); 1292 return true; 1293 } 1294 return false; 1295 } 1296 1297 /** 1298 * Stop any current scroll in progress, such as one started by 1299 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 1300 */ stopScroll()1301 public void stopScroll() { 1302 setScrollState(SCROLL_STATE_IDLE); 1303 stopScrollersInternal(); 1304 } 1305 1306 /** 1307 * Similar to {@link #stopScroll()} but does not set the state. 1308 */ stopScrollersInternal()1309 private void stopScrollersInternal() { 1310 mViewFlinger.stop(); 1311 if (mLayout != null) { 1312 mLayout.stopSmoothScroller(); 1313 } 1314 } 1315 1316 /** 1317 * Apply a pull to relevant overscroll glow effects 1318 */ pullGlows(int overscrollX, int overscrollY)1319 private void pullGlows(int overscrollX, int overscrollY) { 1320 if (overscrollX < 0) { 1321 ensureLeftGlow(); 1322 mLeftGlow.onPull(-overscrollX / (float) getWidth()); 1323 } else if (overscrollX > 0) { 1324 ensureRightGlow(); 1325 mRightGlow.onPull(overscrollX / (float) getWidth()); 1326 } 1327 1328 if (overscrollY < 0) { 1329 ensureTopGlow(); 1330 mTopGlow.onPull(-overscrollY / (float) getHeight()); 1331 } else if (overscrollY > 0) { 1332 ensureBottomGlow(); 1333 mBottomGlow.onPull(overscrollY / (float) getHeight()); 1334 } 1335 1336 if (overscrollX != 0 || overscrollY != 0) { 1337 ViewCompat.postInvalidateOnAnimation(this); 1338 } 1339 } 1340 releaseGlows()1341 private void releaseGlows() { 1342 boolean needsInvalidate = false; 1343 if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease(); 1344 if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease(); 1345 if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease(); 1346 if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease(); 1347 if (needsInvalidate) { 1348 ViewCompat.postInvalidateOnAnimation(this); 1349 } 1350 } 1351 considerReleasingGlowsOnScroll(int dx, int dy)1352 private void considerReleasingGlowsOnScroll(int dx, int dy) { 1353 boolean needsInvalidate = false; 1354 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { 1355 needsInvalidate = mLeftGlow.onRelease(); 1356 } 1357 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { 1358 needsInvalidate |= mRightGlow.onRelease(); 1359 } 1360 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { 1361 needsInvalidate |= mTopGlow.onRelease(); 1362 } 1363 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { 1364 needsInvalidate |= mBottomGlow.onRelease(); 1365 } 1366 if (needsInvalidate) { 1367 ViewCompat.postInvalidateOnAnimation(this); 1368 } 1369 } 1370 absorbGlows(int velocityX, int velocityY)1371 void absorbGlows(int velocityX, int velocityY) { 1372 if (velocityX < 0) { 1373 ensureLeftGlow(); 1374 mLeftGlow.onAbsorb(-velocityX); 1375 } else if (velocityX > 0) { 1376 ensureRightGlow(); 1377 mRightGlow.onAbsorb(velocityX); 1378 } 1379 1380 if (velocityY < 0) { 1381 ensureTopGlow(); 1382 mTopGlow.onAbsorb(-velocityY); 1383 } else if (velocityY > 0) { 1384 ensureBottomGlow(); 1385 mBottomGlow.onAbsorb(velocityY); 1386 } 1387 1388 if (velocityX != 0 || velocityY != 0) { 1389 ViewCompat.postInvalidateOnAnimation(this); 1390 } 1391 } 1392 ensureLeftGlow()1393 void ensureLeftGlow() { 1394 if (mLeftGlow != null) { 1395 return; 1396 } 1397 mLeftGlow = new EdgeEffectCompat(getContext()); 1398 if (mClipToPadding) { 1399 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 1400 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 1401 } else { 1402 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 1403 } 1404 } 1405 ensureRightGlow()1406 void ensureRightGlow() { 1407 if (mRightGlow != null) { 1408 return; 1409 } 1410 mRightGlow = new EdgeEffectCompat(getContext()); 1411 if (mClipToPadding) { 1412 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 1413 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 1414 } else { 1415 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 1416 } 1417 } 1418 ensureTopGlow()1419 void ensureTopGlow() { 1420 if (mTopGlow != null) { 1421 return; 1422 } 1423 mTopGlow = new EdgeEffectCompat(getContext()); 1424 if (mClipToPadding) { 1425 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 1426 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 1427 } else { 1428 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 1429 } 1430 1431 } 1432 ensureBottomGlow()1433 void ensureBottomGlow() { 1434 if (mBottomGlow != null) { 1435 return; 1436 } 1437 mBottomGlow = new EdgeEffectCompat(getContext()); 1438 if (mClipToPadding) { 1439 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 1440 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 1441 } else { 1442 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 1443 } 1444 } 1445 invalidateGlows()1446 void invalidateGlows() { 1447 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; 1448 } 1449 1450 // Focus handling 1451 1452 @Override focusSearch(View focused, int direction)1453 public View focusSearch(View focused, int direction) { 1454 View result = mLayout.onInterceptFocusSearch(focused, direction); 1455 if (result != null) { 1456 return result; 1457 } 1458 final FocusFinder ff = FocusFinder.getInstance(); 1459 result = ff.findNextFocus(this, focused, direction); 1460 if (result == null && mAdapter != null && mLayout != null) { 1461 eatRequestLayout(); 1462 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 1463 resumeRequestLayout(false); 1464 } 1465 return result != null ? result : super.focusSearch(focused, direction); 1466 } 1467 1468 @Override requestChildFocus(View child, View focused)1469 public void requestChildFocus(View child, View focused) { 1470 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { 1471 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 1472 offsetDescendantRectToMyCoords(focused, mTempRect); 1473 offsetRectIntoDescendantCoords(child, mTempRect); 1474 requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete); 1475 } 1476 super.requestChildFocus(child, focused); 1477 } 1478 1479 @Override requestChildRectangleOnScreen(View child, Rect rect, boolean immediate)1480 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 1481 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); 1482 } 1483 1484 @Override addFocusables(ArrayList<View> views, int direction, int focusableMode)1485 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1486 if (!mLayout.onAddFocusables(this, views, direction, focusableMode)) { 1487 super.addFocusables(views, direction, focusableMode); 1488 } 1489 } 1490 1491 @Override onAttachedToWindow()1492 protected void onAttachedToWindow() { 1493 super.onAttachedToWindow(); 1494 mIsAttached = true; 1495 mFirstLayoutComplete = false; 1496 if (mLayout != null) { 1497 mLayout.onAttachedToWindow(this); 1498 } 1499 mPostedAnimatorRunner = false; 1500 } 1501 1502 @Override onDetachedFromWindow()1503 protected void onDetachedFromWindow() { 1504 super.onDetachedFromWindow(); 1505 if (mItemAnimator != null) { 1506 mItemAnimator.endAnimations(); 1507 } 1508 mFirstLayoutComplete = false; 1509 1510 stopScroll(); 1511 mIsAttached = false; 1512 if (mLayout != null) { 1513 mLayout.onDetachedFromWindow(this, mRecycler); 1514 } 1515 removeCallbacks(mItemAnimatorRunner); 1516 } 1517 1518 /** 1519 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 1520 * {@link IllegalStateException} if it <b>is not</b>. 1521 * 1522 * @param message The message for the exception. Can be null. 1523 * @see #assertNotInLayoutOrScroll(String) 1524 */ assertInLayoutOrScroll(String message)1525 void assertInLayoutOrScroll(String message) { 1526 if (!mRunningLayoutOrScroll) { 1527 if (message == null) { 1528 throw new IllegalStateException("Cannot call this method unless RecyclerView is " 1529 + "computing a layout or scrolling"); 1530 } 1531 throw new IllegalStateException(message); 1532 1533 } 1534 } 1535 1536 /** 1537 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 1538 * {@link IllegalStateException} if it <b>is</b>. 1539 * 1540 * @param message The message for the exception. Can be null. 1541 * @see #assertInLayoutOrScroll(String) 1542 */ assertNotInLayoutOrScroll(String message)1543 void assertNotInLayoutOrScroll(String message) { 1544 if (mRunningLayoutOrScroll) { 1545 if (message == null) { 1546 throw new IllegalStateException("Cannot call this method while RecyclerView is " 1547 + "computing a layout or scrolling"); 1548 } 1549 throw new IllegalStateException(message); 1550 } 1551 } 1552 1553 /** 1554 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 1555 * to child views or this view's standard scrolling behavior. 1556 * 1557 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 1558 * returns true from 1559 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 1560 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 1561 * for each incoming MotionEvent until the end of the gesture.</p> 1562 * 1563 * @param listener Listener to add 1564 */ addOnItemTouchListener(OnItemTouchListener listener)1565 public void addOnItemTouchListener(OnItemTouchListener listener) { 1566 mOnItemTouchListeners.add(listener); 1567 } 1568 1569 /** 1570 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 1571 * 1572 * @param listener Listener to remove 1573 */ removeOnItemTouchListener(OnItemTouchListener listener)1574 public void removeOnItemTouchListener(OnItemTouchListener listener) { 1575 mOnItemTouchListeners.remove(listener); 1576 if (mActiveOnItemTouchListener == listener) { 1577 mActiveOnItemTouchListener = null; 1578 } 1579 } 1580 dispatchOnItemTouchIntercept(MotionEvent e)1581 private boolean dispatchOnItemTouchIntercept(MotionEvent e) { 1582 final int action = e.getAction(); 1583 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { 1584 mActiveOnItemTouchListener = null; 1585 } 1586 1587 final int listenerCount = mOnItemTouchListeners.size(); 1588 for (int i = 0; i < listenerCount; i++) { 1589 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 1590 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 1591 mActiveOnItemTouchListener = listener; 1592 return true; 1593 } 1594 } 1595 return false; 1596 } 1597 dispatchOnItemTouch(MotionEvent e)1598 private boolean dispatchOnItemTouch(MotionEvent e) { 1599 final int action = e.getAction(); 1600 if (mActiveOnItemTouchListener != null) { 1601 if (action == MotionEvent.ACTION_DOWN) { 1602 // Stale state from a previous gesture, we're starting a new one. Clear it. 1603 mActiveOnItemTouchListener = null; 1604 } else { 1605 mActiveOnItemTouchListener.onTouchEvent(this, e); 1606 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1607 // Clean up for the next gesture. 1608 mActiveOnItemTouchListener = null; 1609 } 1610 return true; 1611 } 1612 } 1613 1614 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept 1615 // as called from onInterceptTouchEvent; skip it. 1616 if (action != MotionEvent.ACTION_DOWN) { 1617 final int listenerCount = mOnItemTouchListeners.size(); 1618 for (int i = 0; i < listenerCount; i++) { 1619 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 1620 if (listener.onInterceptTouchEvent(this, e)) { 1621 mActiveOnItemTouchListener = listener; 1622 return true; 1623 } 1624 } 1625 } 1626 return false; 1627 } 1628 1629 @Override onInterceptTouchEvent(MotionEvent e)1630 public boolean onInterceptTouchEvent(MotionEvent e) { 1631 if (dispatchOnItemTouchIntercept(e)) { 1632 cancelTouch(); 1633 return true; 1634 } 1635 1636 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 1637 final boolean canScrollVertically = mLayout.canScrollVertically(); 1638 1639 if (mVelocityTracker == null) { 1640 mVelocityTracker = VelocityTracker.obtain(); 1641 } 1642 mVelocityTracker.addMovement(e); 1643 1644 final int action = MotionEventCompat.getActionMasked(e); 1645 final int actionIndex = MotionEventCompat.getActionIndex(e); 1646 1647 switch (action) { 1648 case MotionEvent.ACTION_DOWN: 1649 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 1650 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 1651 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 1652 1653 if (mScrollState == SCROLL_STATE_SETTLING) { 1654 getParent().requestDisallowInterceptTouchEvent(true); 1655 setScrollState(SCROLL_STATE_DRAGGING); 1656 } 1657 break; 1658 1659 case MotionEventCompat.ACTION_POINTER_DOWN: 1660 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 1661 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 1662 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 1663 break; 1664 1665 case MotionEvent.ACTION_MOVE: { 1666 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 1667 if (index < 0) { 1668 Log.e(TAG, "Error processing scroll; pointer index for id " + 1669 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 1670 return false; 1671 } 1672 1673 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 1674 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 1675 if (mScrollState != SCROLL_STATE_DRAGGING) { 1676 final int dx = x - mInitialTouchX; 1677 final int dy = y - mInitialTouchY; 1678 boolean startScroll = false; 1679 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 1680 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 1681 startScroll = true; 1682 } 1683 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 1684 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 1685 startScroll = true; 1686 } 1687 if (startScroll) { 1688 setScrollState(SCROLL_STATE_DRAGGING); 1689 } 1690 } 1691 } break; 1692 1693 case MotionEventCompat.ACTION_POINTER_UP: { 1694 onPointerUp(e); 1695 } break; 1696 1697 case MotionEvent.ACTION_UP: { 1698 mVelocityTracker.clear(); 1699 } break; 1700 1701 case MotionEvent.ACTION_CANCEL: { 1702 cancelTouch(); 1703 } 1704 } 1705 return mScrollState == SCROLL_STATE_DRAGGING; 1706 } 1707 1708 @Override onTouchEvent(MotionEvent e)1709 public boolean onTouchEvent(MotionEvent e) { 1710 if (dispatchOnItemTouch(e)) { 1711 cancelTouch(); 1712 return true; 1713 } 1714 1715 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 1716 final boolean canScrollVertically = mLayout.canScrollVertically(); 1717 1718 if (mVelocityTracker == null) { 1719 mVelocityTracker = VelocityTracker.obtain(); 1720 } 1721 mVelocityTracker.addMovement(e); 1722 1723 final int action = MotionEventCompat.getActionMasked(e); 1724 final int actionIndex = MotionEventCompat.getActionIndex(e); 1725 1726 switch (action) { 1727 case MotionEvent.ACTION_DOWN: { 1728 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 1729 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 1730 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 1731 } break; 1732 1733 case MotionEventCompat.ACTION_POINTER_DOWN: { 1734 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 1735 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 1736 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 1737 } break; 1738 1739 case MotionEvent.ACTION_MOVE: { 1740 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 1741 if (index < 0) { 1742 Log.e(TAG, "Error processing scroll; pointer index for id " + 1743 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 1744 return false; 1745 } 1746 1747 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 1748 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 1749 if (mScrollState != SCROLL_STATE_DRAGGING) { 1750 final int dx = x - mInitialTouchX; 1751 final int dy = y - mInitialTouchY; 1752 boolean startScroll = false; 1753 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 1754 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 1755 startScroll = true; 1756 } 1757 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 1758 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 1759 startScroll = true; 1760 } 1761 if (startScroll) { 1762 setScrollState(SCROLL_STATE_DRAGGING); 1763 } 1764 } 1765 if (mScrollState == SCROLL_STATE_DRAGGING) { 1766 final int dx = x - mLastTouchX; 1767 final int dy = y - mLastTouchY; 1768 if (scrollByInternal( 1769 canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) { 1770 getParent().requestDisallowInterceptTouchEvent(true); 1771 } 1772 } 1773 mLastTouchX = x; 1774 mLastTouchY = y; 1775 } break; 1776 1777 case MotionEventCompat.ACTION_POINTER_UP: { 1778 onPointerUp(e); 1779 } break; 1780 1781 case MotionEvent.ACTION_UP: { 1782 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 1783 final float xvel = canScrollHorizontally ? 1784 -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0; 1785 final float yvel = canScrollVertically ? 1786 -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0; 1787 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 1788 setScrollState(SCROLL_STATE_IDLE); 1789 } 1790 mVelocityTracker.clear(); 1791 releaseGlows(); 1792 } break; 1793 1794 case MotionEvent.ACTION_CANCEL: { 1795 cancelTouch(); 1796 } break; 1797 } 1798 1799 return true; 1800 } 1801 cancelTouch()1802 private void cancelTouch() { 1803 if (mVelocityTracker != null) { 1804 mVelocityTracker.clear(); 1805 } 1806 releaseGlows(); 1807 setScrollState(SCROLL_STATE_IDLE); 1808 } 1809 onPointerUp(MotionEvent e)1810 private void onPointerUp(MotionEvent e) { 1811 final int actionIndex = MotionEventCompat.getActionIndex(e); 1812 if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) { 1813 // Pick a new pointer to pick up the slack. 1814 final int newIndex = actionIndex == 0 ? 1 : 0; 1815 mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex); 1816 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f); 1817 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f); 1818 } 1819 } 1820 1821 @Override onMeasure(int widthSpec, int heightSpec)1822 protected void onMeasure(int widthSpec, int heightSpec) { 1823 if (mAdapterUpdateDuringMeasure) { 1824 eatRequestLayout(); 1825 processAdapterUpdatesAndSetAnimationFlags(); 1826 1827 if (mState.mRunPredictiveAnimations) { 1828 // TODO: try to provide a better approach. 1829 // When RV decides to run predictive animations, we need to measure in pre-layout 1830 // state so that pre-layout pass results in correct layout. 1831 // On the other hand, this will prevent the layout manager from resizing properly. 1832 mState.mInPreLayout = true; 1833 } else { 1834 // consume remaining updates to provide a consistent state with the layout pass. 1835 mAdapterHelper.consumeUpdatesInOnePass(); 1836 mState.mInPreLayout = false; 1837 } 1838 mAdapterUpdateDuringMeasure = false; 1839 resumeRequestLayout(false); 1840 } 1841 1842 if (mAdapter != null) { 1843 mState.mItemCount = mAdapter.getItemCount(); 1844 } else { 1845 mState.mItemCount = 0; 1846 } 1847 if (mLayout == null) { 1848 defaultOnMeasure(widthSpec, heightSpec); 1849 } else { 1850 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 1851 } 1852 1853 mState.mInPreLayout = false; // clear 1854 } 1855 1856 /** 1857 * Used when onMeasure is called before layout manager is set 1858 */ defaultOnMeasure(int widthSpec, int heightSpec)1859 private void defaultOnMeasure(int widthSpec, int heightSpec) { 1860 final int widthMode = MeasureSpec.getMode(widthSpec); 1861 final int heightMode = MeasureSpec.getMode(heightSpec); 1862 final int widthSize = MeasureSpec.getSize(widthSpec); 1863 final int heightSize = MeasureSpec.getSize(heightSpec); 1864 1865 int width = 0; 1866 int height = 0; 1867 1868 switch (widthMode) { 1869 case MeasureSpec.EXACTLY: 1870 case MeasureSpec.AT_MOST: 1871 width = widthSize; 1872 break; 1873 case MeasureSpec.UNSPECIFIED: 1874 default: 1875 width = ViewCompat.getMinimumWidth(this); 1876 break; 1877 } 1878 1879 switch (heightMode) { 1880 case MeasureSpec.EXACTLY: 1881 case MeasureSpec.AT_MOST: 1882 height = heightSize; 1883 break; 1884 case MeasureSpec.UNSPECIFIED: 1885 default: 1886 height = ViewCompat.getMinimumHeight(this); 1887 break; 1888 } 1889 1890 setMeasuredDimension(width, height); 1891 } 1892 1893 @Override onSizeChanged(int w, int h, int oldw, int oldh)1894 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1895 super.onSizeChanged(w, h, oldw, oldh); 1896 if (w != oldw || h != oldh) { 1897 invalidateGlows(); 1898 } 1899 } 1900 1901 /** 1902 * Sets the {@link ItemAnimator} that will handle animations involving changes 1903 * to the items in this RecyclerView. By default, RecyclerView instantiates and 1904 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are 1905 * enabled for the RecyclerView depends on the ItemAnimator and whether 1906 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() 1907 * supports item animations}. 1908 * 1909 * @param animator The ItemAnimator being set. If null, no animations will occur 1910 * when changes occur to the items in this RecyclerView. 1911 */ setItemAnimator(ItemAnimator animator)1912 public void setItemAnimator(ItemAnimator animator) { 1913 if (mItemAnimator != null) { 1914 mItemAnimator.endAnimations(); 1915 mItemAnimator.setListener(null); 1916 } 1917 mItemAnimator = animator; 1918 if (mItemAnimator != null) { 1919 mItemAnimator.setListener(mItemAnimatorListener); 1920 } 1921 } 1922 1923 /** 1924 * Gets the current ItemAnimator for this RecyclerView. A null return value 1925 * indicates that there is no animator and that item changes will happen without 1926 * any animations. By default, RecyclerView instantiates and 1927 * uses an instance of {@link DefaultItemAnimator}. 1928 * 1929 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur 1930 * when changes occur to the items in this RecyclerView. 1931 */ getItemAnimator()1932 public ItemAnimator getItemAnimator() { 1933 return mItemAnimator; 1934 } 1935 supportsChangeAnimations()1936 private boolean supportsChangeAnimations() { 1937 return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations(); 1938 } 1939 1940 /** 1941 * Post a runnable to the next frame to run pending item animations. Only the first such 1942 * request will be posted, governed by the mPostedAnimatorRunner flag. 1943 */ postAnimationRunner()1944 private void postAnimationRunner() { 1945 if (!mPostedAnimatorRunner && mIsAttached) { 1946 ViewCompat.postOnAnimation(this, mItemAnimatorRunner); 1947 mPostedAnimatorRunner = true; 1948 } 1949 } 1950 predictiveItemAnimationsEnabled()1951 private boolean predictiveItemAnimationsEnabled() { 1952 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); 1953 } 1954 1955 /** 1956 * Consumes adapter updates and calculates which type of animations we want to run. 1957 * Called in onMeasure and dispatchLayout. 1958 * <p> 1959 * This method may process only the pre-layout state of updates or all of them. 1960 */ processAdapterUpdatesAndSetAnimationFlags()1961 private void processAdapterUpdatesAndSetAnimationFlags() { 1962 if (mDataSetHasChangedAfterLayout) { 1963 // Processing these items have no value since data set changed unexpectedly. 1964 // Instead, we just reset it. 1965 mAdapterHelper.reset(); 1966 markKnownViewsInvalid(); 1967 mLayout.onItemsChanged(this); 1968 } 1969 // simple animations are a subset of advanced animations (which will cause a 1970 // pre-layout step) 1971 // If layout supports predictive animations, pre-process to decide if we want to run them 1972 if (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) { 1973 mAdapterHelper.preProcess(); 1974 } else { 1975 mAdapterHelper.consumeUpdatesInOnePass(); 1976 } 1977 boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) || 1978 (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations())); 1979 mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && 1980 (mDataSetHasChangedAfterLayout || animationTypeSupported || 1981 mLayout.mRequestedSimpleAnimations) && 1982 (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); 1983 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && 1984 animationTypeSupported && !mDataSetHasChangedAfterLayout && 1985 predictiveItemAnimationsEnabled(); 1986 } 1987 1988 /** 1989 * Wrapper around layoutChildren() that handles animating changes caused by layout. 1990 * Animations work on the assumption that there are five different kinds of items 1991 * in play: 1992 * PERSISTENT: items are visible before and after layout 1993 * REMOVED: items were visible before layout and were removed by the app 1994 * ADDED: items did not exist before layout and were added by the app 1995 * DISAPPEARING: items exist in the data set before/after, but changed from 1996 * visible to non-visible in the process of layout (they were moved off 1997 * screen as a side-effect of other changes) 1998 * APPEARING: items exist in the data set before/after, but changed from 1999 * non-visible to visible in the process of layout (they were moved on 2000 * screen as a side-effect of other changes) 2001 * The overall approach figures out what items exist before/after layout and 2002 * infers one of the five above states for each of the items. Then the animations 2003 * are set up accordingly: 2004 * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)}) 2005 * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)}) 2006 * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)}) 2007 * DISAPPEARING views are moved off screen 2008 * APPEARING views are moved on screen 2009 */ dispatchLayout()2010 void dispatchLayout() { 2011 if (mAdapter == null) { 2012 Log.e(TAG, "No adapter attached; skipping layout"); 2013 return; 2014 } 2015 if (mLayout == null) { 2016 Log.e(TAG, "No layout manager attached; skipping layout"); 2017 return; 2018 } 2019 mDisappearingViewsInLayoutPass.clear(); 2020 eatRequestLayout(); 2021 mRunningLayoutOrScroll = true; 2022 2023 processAdapterUpdatesAndSetAnimationFlags(); 2024 2025 mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged 2026 && supportsChangeAnimations() ? new ArrayMap<Long, ViewHolder>() : null; 2027 mItemsAddedOrRemoved = mItemsChanged = false; 2028 ArrayMap<View, Rect> appearingViewInitialBounds = null; 2029 mState.mInPreLayout = mState.mRunPredictiveAnimations; 2030 mState.mItemCount = mAdapter.getItemCount(); 2031 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 2032 2033 if (mState.mRunSimpleAnimations) { 2034 // Step 0: Find out where all non-removed items are, pre-layout 2035 mState.mPreLayoutHolderMap.clear(); 2036 mState.mPostLayoutHolderMap.clear(); 2037 int count = mChildHelper.getChildCount(); 2038 for (int i = 0; i < count; ++i) { 2039 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2040 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { 2041 continue; 2042 } 2043 final View view = holder.itemView; 2044 mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder, 2045 view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 2046 } 2047 } 2048 if (mState.mRunPredictiveAnimations) { 2049 // Step 1: run prelayout: This will use the old positions of items. The layout manager 2050 // is expected to layout everything, even removed items (though not to add removed 2051 // items back to the container). This gives the pre-layout position of APPEARING views 2052 // which come into existence as part of the real layout. 2053 2054 // Save old positions so that LayoutManager can run its mapping logic. 2055 saveOldPositions(); 2056 // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations. 2057 if (mState.mOldChangedHolders != null) { 2058 int count = mChildHelper.getChildCount(); 2059 for (int i = 0; i < count; ++i) { 2060 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2061 if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { 2062 long key = getChangedHolderKey(holder); 2063 mState.mOldChangedHolders.put(key, holder); 2064 mState.mPreLayoutHolderMap.remove(holder); 2065 } 2066 } 2067 } 2068 2069 final boolean didStructureChange = mState.mStructureChanged; 2070 mState.mStructureChanged = false; 2071 // temporarily disable flag because we are asking for previous layout 2072 mLayout.onLayoutChildren(mRecycler, mState); 2073 mState.mStructureChanged = didStructureChange; 2074 2075 appearingViewInitialBounds = new ArrayMap<View, Rect>(); 2076 for (int i = 0; i < mChildHelper.getChildCount(); ++i) { 2077 boolean found = false; 2078 View child = mChildHelper.getChildAt(i); 2079 if (getChildViewHolderInt(child).shouldIgnore()) { 2080 continue; 2081 } 2082 for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) { 2083 ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j); 2084 if (holder.itemView == child) { 2085 found = true; 2086 break; 2087 } 2088 } 2089 if (!found) { 2090 appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(), 2091 child.getRight(), child.getBottom())); 2092 } 2093 } 2094 // we don't process disappearing list because they may re-appear in post layout pass. 2095 clearOldPositions(); 2096 mAdapterHelper.consumePostponedUpdates(); 2097 } else { 2098 clearOldPositions(); 2099 // in case pre layout did run but we decided not to run predictive animations. 2100 mAdapterHelper.consumeUpdatesInOnePass(); 2101 if (mState.mOldChangedHolders != null) { 2102 int count = mChildHelper.getChildCount(); 2103 for (int i = 0; i < count; ++i) { 2104 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2105 if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { 2106 long key = getChangedHolderKey(holder); 2107 mState.mOldChangedHolders.put(key, holder); 2108 mState.mPreLayoutHolderMap.remove(holder); 2109 } 2110 } 2111 } 2112 } 2113 mState.mItemCount = mAdapter.getItemCount(); 2114 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; 2115 2116 // Step 2: Run layout 2117 mState.mInPreLayout = false; 2118 mLayout.onLayoutChildren(mRecycler, mState); 2119 2120 mState.mStructureChanged = false; 2121 mPendingSavedState = null; 2122 2123 // onLayoutChildren may have caused client code to disable item animations; re-check 2124 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; 2125 2126 if (mState.mRunSimpleAnimations) { 2127 // Step 3: Find out where things are now, post-layout 2128 ArrayMap<Long, ViewHolder> newChangedHolders = mState.mOldChangedHolders != null ? 2129 new ArrayMap<Long, ViewHolder>() : null; 2130 int count = mChildHelper.getChildCount(); 2131 for (int i = 0; i < count; ++i) { 2132 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2133 if (holder.shouldIgnore()) { 2134 continue; 2135 } 2136 final View view = holder.itemView; 2137 long key = getChangedHolderKey(holder); 2138 if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) { 2139 newChangedHolders.put(key, holder); 2140 } else { 2141 mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder, 2142 view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 2143 } 2144 } 2145 processDisappearingList(appearingViewInitialBounds); 2146 // Step 4: Animate DISAPPEARING and REMOVED items 2147 int preLayoutCount = mState.mPreLayoutHolderMap.size(); 2148 for (int i = preLayoutCount - 1; i >= 0; i--) { 2149 ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i); 2150 if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) { 2151 ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i); 2152 mState.mPreLayoutHolderMap.removeAt(i); 2153 2154 View disappearingItemView = disappearingItem.holder.itemView; 2155 mRecycler.unscrapView(disappearingItem.holder); 2156 animateDisappearance(disappearingItem); 2157 } 2158 } 2159 // Step 5: Animate APPEARING and ADDED items 2160 int postLayoutCount = mState.mPostLayoutHolderMap.size(); 2161 if (postLayoutCount > 0) { 2162 for (int i = postLayoutCount - 1; i >= 0; i--) { 2163 ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i); 2164 ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i); 2165 if ((mState.mPreLayoutHolderMap.isEmpty() || 2166 !mState.mPreLayoutHolderMap.containsKey(itemHolder))) { 2167 mState.mPostLayoutHolderMap.removeAt(i); 2168 Rect initialBounds = (appearingViewInitialBounds != null) ? 2169 appearingViewInitialBounds.get(itemHolder.itemView) : null; 2170 animateAppearance(itemHolder, initialBounds, 2171 info.left, info.top); 2172 } 2173 } 2174 } 2175 // Step 6: Animate PERSISTENT items 2176 count = mState.mPostLayoutHolderMap.size(); 2177 for (int i = 0; i < count; ++i) { 2178 ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i); 2179 ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i); 2180 ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder); 2181 if (preInfo != null && postInfo != null) { 2182 if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) { 2183 postHolder.setIsRecyclable(false); 2184 if (DEBUG) { 2185 Log.d(TAG, "PERSISTENT: " + postHolder + 2186 " with view " + postHolder.itemView); 2187 } 2188 if (mItemAnimator.animateMove(postHolder, 2189 preInfo.left, preInfo.top, postInfo.left, postInfo.top)) { 2190 postAnimationRunner(); 2191 } 2192 } 2193 } 2194 } 2195 // Step 7: Animate CHANGING items 2196 count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0; 2197 // traverse reverse in case view gets recycled while we are traversing the list. 2198 for (int i = count - 1; i >= 0; i--) { 2199 long key = mState.mOldChangedHolders.keyAt(i); 2200 ViewHolder oldHolder = mState.mOldChangedHolders.get(key); 2201 View oldView = oldHolder.itemView; 2202 if (oldHolder.shouldIgnore()) { 2203 continue; 2204 } 2205 // We probably don't need this check anymore since these views are removed from 2206 // the list if they are recycled. 2207 if (mRecycler.mChangedScrap != null && 2208 mRecycler.mChangedScrap.contains(oldHolder)) { 2209 animateChange(oldHolder, newChangedHolders.get(key)); 2210 } else if (DEBUG) { 2211 Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder); 2212 } 2213 } 2214 } 2215 resumeRequestLayout(false); 2216 mLayout.removeAndRecycleScrapInt(mRecycler); 2217 mState.mPreviousLayoutItemCount = mState.mItemCount; 2218 mDataSetHasChangedAfterLayout = false; 2219 mState.mRunSimpleAnimations = false; 2220 mState.mRunPredictiveAnimations = false; 2221 mRunningLayoutOrScroll = false; 2222 mLayout.mRequestedSimpleAnimations = false; 2223 if (mRecycler.mChangedScrap != null) { 2224 mRecycler.mChangedScrap.clear(); 2225 } 2226 mState.mOldChangedHolders = null; 2227 2228 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { 2229 notifyOnScrolled(0, 0); 2230 } 2231 } 2232 findMinMaxChildLayoutPositions(int[] into)2233 private void findMinMaxChildLayoutPositions(int[] into) { 2234 final int count = mChildHelper.getChildCount(); 2235 if (count == 0) { 2236 into[0] = 0; 2237 into[1] = 0; 2238 return; 2239 } 2240 int minPositionPreLayout = Integer.MAX_VALUE; 2241 int maxPositionPreLayout = Integer.MIN_VALUE; 2242 for (int i = 0; i < count; ++i) { 2243 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2244 if (holder.shouldIgnore()) { 2245 continue; 2246 } 2247 final int pos = holder.getLayoutPosition(); 2248 if (pos < minPositionPreLayout) { 2249 minPositionPreLayout = pos; 2250 } 2251 if (pos > maxPositionPreLayout) { 2252 maxPositionPreLayout = pos; 2253 } 2254 } 2255 into[0] = minPositionPreLayout; 2256 into[1] = maxPositionPreLayout; 2257 } 2258 didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout)2259 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { 2260 int count = mChildHelper.getChildCount(); 2261 if (count == 0) { 2262 return minPositionPreLayout != 0 || maxPositionPreLayout != 0; 2263 } 2264 for (int i = 0; i < count; ++i) { 2265 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2266 if (holder.shouldIgnore()) { 2267 continue; 2268 } 2269 final int pos = holder.getLayoutPosition(); 2270 if (pos < minPositionPreLayout || pos > maxPositionPreLayout) { 2271 return true; 2272 } 2273 } 2274 return false; 2275 } 2276 2277 @Override removeDetachedView(View child, boolean animate)2278 protected void removeDetachedView(View child, boolean animate) { 2279 ViewHolder vh = getChildViewHolderInt(child); 2280 if (vh != null) { 2281 if (vh.isTmpDetached()) { 2282 vh.clearTmpDetachFlag(); 2283 } else if (!vh.shouldIgnore()) { 2284 throw new IllegalArgumentException("Called removeDetachedView with a view which" 2285 + " is not flagged as tmp detached." + vh); 2286 } 2287 } 2288 dispatchChildDetached(child); 2289 super.removeDetachedView(child, animate); 2290 } 2291 2292 /** 2293 * Returns a unique key to be used while handling change animations. 2294 * It might be child's position or stable id depending on the adapter type. 2295 */ getChangedHolderKey(ViewHolder holder)2296 long getChangedHolderKey(ViewHolder holder) { 2297 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; 2298 } 2299 2300 /** 2301 * A LayoutManager may want to layout a view just to animate disappearance. 2302 * This method handles those views and triggers remove animation on them. 2303 */ processDisappearingList(ArrayMap<View, Rect> appearingViews)2304 private void processDisappearingList(ArrayMap<View, Rect> appearingViews) { 2305 final int count = mDisappearingViewsInLayoutPass.size(); 2306 for (int i = 0; i < count; i ++) { 2307 View view = mDisappearingViewsInLayoutPass.get(i); 2308 ViewHolder vh = getChildViewHolderInt(view); 2309 final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh); 2310 if (!mState.isPreLayout()) { 2311 mState.mPostLayoutHolderMap.remove(vh); 2312 } 2313 if (appearingViews.remove(view) != null) { 2314 mLayout.removeAndRecycleView(view, mRecycler); 2315 continue; 2316 } 2317 if (info != null) { 2318 animateDisappearance(info); 2319 } else { 2320 // let it disappear from the position it becomes visible 2321 animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(), 2322 view.getRight(), view.getBottom())); 2323 } 2324 } 2325 mDisappearingViewsInLayoutPass.clear(); 2326 } 2327 animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft, int afterTop)2328 private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft, 2329 int afterTop) { 2330 View newItemView = itemHolder.itemView; 2331 if (beforeBounds != null && 2332 (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) { 2333 // slide items in if before/after locations differ 2334 itemHolder.setIsRecyclable(false); 2335 if (DEBUG) { 2336 Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView); 2337 } 2338 if (mItemAnimator.animateMove(itemHolder, 2339 beforeBounds.left, beforeBounds.top, 2340 afterLeft, afterTop)) { 2341 postAnimationRunner(); 2342 } 2343 } else { 2344 if (DEBUG) { 2345 Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView); 2346 } 2347 itemHolder.setIsRecyclable(false); 2348 if (mItemAnimator.animateAdd(itemHolder)) { 2349 postAnimationRunner(); 2350 } 2351 } 2352 } 2353 animateDisappearance(ItemHolderInfo disappearingItem)2354 private void animateDisappearance(ItemHolderInfo disappearingItem) { 2355 View disappearingItemView = disappearingItem.holder.itemView; 2356 addAnimatingView(disappearingItem.holder); 2357 int oldLeft = disappearingItem.left; 2358 int oldTop = disappearingItem.top; 2359 int newLeft = disappearingItemView.getLeft(); 2360 int newTop = disappearingItemView.getTop(); 2361 if (oldLeft != newLeft || oldTop != newTop) { 2362 disappearingItem.holder.setIsRecyclable(false); 2363 disappearingItemView.layout(newLeft, newTop, 2364 newLeft + disappearingItemView.getWidth(), 2365 newTop + disappearingItemView.getHeight()); 2366 if (DEBUG) { 2367 Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder + 2368 " with view " + disappearingItemView); 2369 } 2370 if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop, 2371 newLeft, newTop)) { 2372 postAnimationRunner(); 2373 } 2374 } else { 2375 if (DEBUG) { 2376 Log.d(TAG, "REMOVED: " + disappearingItem.holder + 2377 " with view " + disappearingItemView); 2378 } 2379 disappearingItem.holder.setIsRecyclable(false); 2380 if (mItemAnimator.animateRemove(disappearingItem.holder)) { 2381 postAnimationRunner(); 2382 } 2383 } 2384 } 2385 animateChange(ViewHolder oldHolder, ViewHolder newHolder)2386 private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) { 2387 oldHolder.setIsRecyclable(false); 2388 addAnimatingView(oldHolder); 2389 oldHolder.mShadowedHolder = newHolder; 2390 mRecycler.unscrapView(oldHolder); 2391 if (DEBUG) { 2392 Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); 2393 } 2394 final int fromLeft = oldHolder.itemView.getLeft(); 2395 final int fromTop = oldHolder.itemView.getTop(); 2396 final int toLeft, toTop; 2397 if (newHolder == null || newHolder.shouldIgnore()) { 2398 toLeft = fromLeft; 2399 toTop = fromTop; 2400 } else { 2401 toLeft = newHolder.itemView.getLeft(); 2402 toTop = newHolder.itemView.getTop(); 2403 newHolder.setIsRecyclable(false); 2404 newHolder.mShadowingHolder = oldHolder; 2405 } 2406 if(mItemAnimator.animateChange(oldHolder, newHolder, 2407 fromLeft, fromTop, toLeft, toTop)) { 2408 postAnimationRunner(); 2409 } 2410 } 2411 2412 @Override onLayout(boolean changed, int l, int t, int r, int b)2413 protected void onLayout(boolean changed, int l, int t, int r, int b) { 2414 eatRequestLayout(); 2415 dispatchLayout(); 2416 resumeRequestLayout(false); 2417 mFirstLayoutComplete = true; 2418 } 2419 2420 @Override requestLayout()2421 public void requestLayout() { 2422 if (!mEatRequestLayout) { 2423 super.requestLayout(); 2424 } else { 2425 mLayoutRequestEaten = true; 2426 } 2427 } 2428 markItemDecorInsetsDirty()2429 void markItemDecorInsetsDirty() { 2430 final int childCount = mChildHelper.getUnfilteredChildCount(); 2431 for (int i = 0; i < childCount; i++) { 2432 final View child = mChildHelper.getUnfilteredChildAt(i); 2433 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 2434 } 2435 mRecycler.markItemDecorInsetsDirty(); 2436 } 2437 2438 @Override draw(Canvas c)2439 public void draw(Canvas c) { 2440 super.draw(c); 2441 2442 final int count = mItemDecorations.size(); 2443 for (int i = 0; i < count; i++) { 2444 mItemDecorations.get(i).onDrawOver(c, this, mState); 2445 } 2446 // TODO If padding is not 0 and chilChildrenToPadding is false, to draw glows properly, we 2447 // need find children closest to edges. Not sure if it is worth the effort. 2448 boolean needsInvalidate = false; 2449 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 2450 final int restore = c.save(); 2451 final int padding = mClipToPadding ? getPaddingBottom() : 0; 2452 c.rotate(270); 2453 c.translate(-getHeight() + padding, 0); 2454 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 2455 c.restoreToCount(restore); 2456 } 2457 if (mTopGlow != null && !mTopGlow.isFinished()) { 2458 final int restore = c.save(); 2459 if (mClipToPadding) { 2460 c.translate(getPaddingLeft(), getPaddingTop()); 2461 } 2462 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 2463 c.restoreToCount(restore); 2464 } 2465 if (mRightGlow != null && !mRightGlow.isFinished()) { 2466 final int restore = c.save(); 2467 final int width = getWidth(); 2468 final int padding = mClipToPadding ? getPaddingTop() : 0; 2469 c.rotate(90); 2470 c.translate(-padding, -width); 2471 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 2472 c.restoreToCount(restore); 2473 } 2474 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 2475 final int restore = c.save(); 2476 c.rotate(180); 2477 if (mClipToPadding) { 2478 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); 2479 } else { 2480 c.translate(-getWidth(), -getHeight()); 2481 } 2482 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 2483 c.restoreToCount(restore); 2484 } 2485 2486 // If some views are animating, ItemDecorators are likely to move/change with them. 2487 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's 2488 // display lists are not invalidated. 2489 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 && 2490 mItemAnimator.isRunning()) { 2491 needsInvalidate = true; 2492 } 2493 2494 if (needsInvalidate) { 2495 ViewCompat.postInvalidateOnAnimation(this); 2496 } 2497 } 2498 2499 @Override onDraw(Canvas c)2500 public void onDraw(Canvas c) { 2501 super.onDraw(c); 2502 2503 final int count = mItemDecorations.size(); 2504 for (int i = 0; i < count; i++) { 2505 mItemDecorations.get(i).onDraw(c, this, mState); 2506 } 2507 } 2508 2509 @Override checkLayoutParams(ViewGroup.LayoutParams p)2510 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2511 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 2512 } 2513 2514 @Override generateDefaultLayoutParams()2515 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 2516 if (mLayout == null) { 2517 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2518 } 2519 return mLayout.generateDefaultLayoutParams(); 2520 } 2521 2522 @Override generateLayoutParams(AttributeSet attrs)2523 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 2524 if (mLayout == null) { 2525 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2526 } 2527 return mLayout.generateLayoutParams(getContext(), attrs); 2528 } 2529 2530 @Override generateLayoutParams(ViewGroup.LayoutParams p)2531 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2532 if (mLayout == null) { 2533 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2534 } 2535 return mLayout.generateLayoutParams(p); 2536 } 2537 saveOldPositions()2538 void saveOldPositions() { 2539 final int childCount = mChildHelper.getUnfilteredChildCount(); 2540 for (int i = 0; i < childCount; i++) { 2541 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2542 if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { 2543 throw new IllegalStateException("view holder cannot have position -1 unless it" 2544 + " is removed"); 2545 } 2546 if (!holder.shouldIgnore()) { 2547 holder.saveOldPosition(); 2548 } 2549 } 2550 } 2551 clearOldPositions()2552 void clearOldPositions() { 2553 final int childCount = mChildHelper.getUnfilteredChildCount(); 2554 for (int i = 0; i < childCount; i++) { 2555 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2556 if (!holder.shouldIgnore()) { 2557 holder.clearOldPosition(); 2558 } 2559 } 2560 mRecycler.clearOldPositions(); 2561 } 2562 offsetPositionRecordsForMove(int from, int to)2563 void offsetPositionRecordsForMove(int from, int to) { 2564 final int childCount = mChildHelper.getUnfilteredChildCount(); 2565 final int start, end, inBetweenOffset; 2566 if (from < to) { 2567 start = from; 2568 end = to; 2569 inBetweenOffset = -1; 2570 } else { 2571 start = to; 2572 end = from; 2573 inBetweenOffset = 1; 2574 } 2575 2576 for (int i = 0; i < childCount; i++) { 2577 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2578 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 2579 continue; 2580 } 2581 if (DEBUG) { 2582 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " + 2583 holder); 2584 } 2585 if (holder.mPosition == from) { 2586 holder.offsetPosition(to - from, false); 2587 } else { 2588 holder.offsetPosition(inBetweenOffset, false); 2589 } 2590 2591 mState.mStructureChanged = true; 2592 } 2593 mRecycler.offsetPositionRecordsForMove(from, to); 2594 requestLayout(); 2595 } 2596 offsetPositionRecordsForInsert(int positionStart, int itemCount)2597 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 2598 final int childCount = mChildHelper.getUnfilteredChildCount(); 2599 for (int i = 0; i < childCount; i++) { 2600 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2601 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { 2602 if (DEBUG) { 2603 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + 2604 holder + " now at position " + (holder.mPosition + itemCount)); 2605 } 2606 holder.offsetPosition(itemCount, false); 2607 mState.mStructureChanged = true; 2608 } 2609 } 2610 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 2611 requestLayout(); 2612 } 2613 offsetPositionRecordsForRemove(int positionStart, int itemCount, boolean applyToPreLayout)2614 void offsetPositionRecordsForRemove(int positionStart, int itemCount, 2615 boolean applyToPreLayout) { 2616 final int positionEnd = positionStart + itemCount; 2617 final int childCount = mChildHelper.getUnfilteredChildCount(); 2618 for (int i = 0; i < childCount; i++) { 2619 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2620 if (holder != null && !holder.shouldIgnore()) { 2621 if (holder.mPosition >= positionEnd) { 2622 if (DEBUG) { 2623 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 2624 " holder " + holder + " now at position " + 2625 (holder.mPosition - itemCount)); 2626 } 2627 holder.offsetPosition(-itemCount, applyToPreLayout); 2628 mState.mStructureChanged = true; 2629 } else if (holder.mPosition >= positionStart) { 2630 if (DEBUG) { 2631 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 2632 " holder " + holder + " now REMOVED"); 2633 } 2634 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, 2635 applyToPreLayout); 2636 mState.mStructureChanged = true; 2637 } 2638 } 2639 } 2640 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); 2641 requestLayout(); 2642 } 2643 2644 /** 2645 * Rebind existing views for the given range, or create as needed. 2646 * 2647 * @param positionStart Adapter position to start at 2648 * @param itemCount Number of views that must explicitly be rebound 2649 */ viewRangeUpdate(int positionStart, int itemCount)2650 void viewRangeUpdate(int positionStart, int itemCount) { 2651 final int childCount = mChildHelper.getUnfilteredChildCount(); 2652 final int positionEnd = positionStart + itemCount; 2653 2654 for (int i = 0; i < childCount; i++) { 2655 final View child = mChildHelper.getUnfilteredChildAt(i); 2656 final ViewHolder holder = getChildViewHolderInt(child); 2657 if (holder == null || holder.shouldIgnore()) { 2658 continue; 2659 } 2660 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 2661 // We re-bind these view holders after pre-processing is complete so that 2662 // ViewHolders have their final positions assigned. 2663 holder.addFlags(ViewHolder.FLAG_UPDATE); 2664 if (supportsChangeAnimations()) { 2665 holder.addFlags(ViewHolder.FLAG_CHANGED); 2666 } 2667 // lp cannot be null since we get ViewHolder from it. 2668 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 2669 } 2670 } 2671 mRecycler.viewRangeUpdate(positionStart, itemCount); 2672 } 2673 rebindUpdatedViewHolders()2674 void rebindUpdatedViewHolders() { 2675 final int childCount = mChildHelper.getChildCount(); 2676 for (int i = 0; i < childCount; i++) { 2677 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2678 // validate type is correct 2679 if (holder == null || holder.shouldIgnore()) { 2680 continue; 2681 } 2682 if (holder.isRemoved() || holder.isInvalid()) { 2683 requestLayout(); 2684 } else if (holder.needsUpdate()) { 2685 final int type = mAdapter.getItemViewType(holder.mPosition); 2686 if (holder.getItemViewType() == type) { 2687 // Binding an attached view will request a layout if needed. 2688 if (!holder.isChanged() || !supportsChangeAnimations()) { 2689 mAdapter.bindViewHolder(holder, holder.mPosition); 2690 } else { 2691 // Don't rebind changed holders if change animations are enabled. 2692 // We want the old contents for the animation and will get a new 2693 // holder for the new contents. 2694 requestLayout(); 2695 } 2696 } else { 2697 // binding to a new view will need re-layout anyways. We can as well trigger 2698 // it here so that it happens during layout 2699 holder.addFlags(ViewHolder.FLAG_INVALID); 2700 requestLayout(); 2701 } 2702 } 2703 } 2704 } 2705 setDataSetChangedAfterLayout()2706 private void setDataSetChangedAfterLayout() { 2707 if (mDataSetHasChangedAfterLayout) { 2708 return; 2709 } 2710 mDataSetHasChangedAfterLayout = true; 2711 final int childCount = mChildHelper.getUnfilteredChildCount(); 2712 for (int i = 0; i < childCount; i++) { 2713 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2714 if (holder != null && !holder.shouldIgnore()) { 2715 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 2716 } 2717 } 2718 mRecycler.setAdapterPositionsAsUnknown(); 2719 } 2720 2721 /** 2722 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 2723 * data change event. 2724 */ markKnownViewsInvalid()2725 void markKnownViewsInvalid() { 2726 final int childCount = mChildHelper.getUnfilteredChildCount(); 2727 for (int i = 0; i < childCount; i++) { 2728 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2729 if (holder != null && !holder.shouldIgnore()) { 2730 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 2731 } 2732 } 2733 markItemDecorInsetsDirty(); 2734 mRecycler.markKnownViewsInvalid(); 2735 } 2736 2737 /** 2738 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method 2739 * will trigger a {@link #requestLayout()} call. 2740 */ invalidateItemDecorations()2741 public void invalidateItemDecorations() { 2742 if (mItemDecorations.size() == 0) { 2743 return; 2744 } 2745 if (mLayout != null) { 2746 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" 2747 + " or layout"); 2748 } 2749 markItemDecorInsetsDirty(); 2750 requestLayout(); 2751 } 2752 2753 /** 2754 * Retrieve the {@link ViewHolder} for the given child view. 2755 * 2756 * @param child Child of this RecyclerView to query for its ViewHolder 2757 * @return The child view's ViewHolder 2758 */ getChildViewHolder(View child)2759 public ViewHolder getChildViewHolder(View child) { 2760 final ViewParent parent = child.getParent(); 2761 if (parent != null && parent != this) { 2762 throw new IllegalArgumentException("View " + child + " is not a direct child of " + 2763 this); 2764 } 2765 return getChildViewHolderInt(child); 2766 } 2767 getChildViewHolderInt(View child)2768 static ViewHolder getChildViewHolderInt(View child) { 2769 if (child == null) { 2770 return null; 2771 } 2772 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 2773 } 2774 2775 /** 2776 * @deprecated use {@link #getChildAdapterPosition(View)} or 2777 * {@link #getChildLayoutPosition(View)}. 2778 */ 2779 @Deprecated getChildPosition(View child)2780 public int getChildPosition(View child) { 2781 return getChildAdapterPosition(child); 2782 } 2783 2784 /** 2785 * Return the adapter position that the given child view corresponds to. 2786 * 2787 * @param child Child View to query 2788 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 2789 */ getChildAdapterPosition(View child)2790 public int getChildAdapterPosition(View child) { 2791 final ViewHolder holder = getChildViewHolderInt(child); 2792 return holder != null ? holder.getAdapterPosition() : NO_POSITION; 2793 } 2794 2795 /** 2796 * Return the adapter position of the given child view as of the latest completed layout pass. 2797 * <p> 2798 * This position may not be equal to Item's adapter position if there are pending changes 2799 * in the adapter which have not been reflected to the layout yet. 2800 * 2801 * @param child Child View to query 2802 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if 2803 * the View is representing a removed item. 2804 */ getChildLayoutPosition(View child)2805 public int getChildLayoutPosition(View child) { 2806 final ViewHolder holder = getChildViewHolderInt(child); 2807 return holder != null ? holder.getLayoutPosition() : NO_POSITION; 2808 } 2809 2810 /** 2811 * Return the stable item id that the given child view corresponds to. 2812 * 2813 * @param child Child View to query 2814 * @return Item id corresponding to the given view or {@link #NO_ID} 2815 */ getChildItemId(View child)2816 public long getChildItemId(View child) { 2817 if (mAdapter == null || !mAdapter.hasStableIds()) { 2818 return NO_ID; 2819 } 2820 final ViewHolder holder = getChildViewHolderInt(child); 2821 return holder != null ? holder.getItemId() : NO_ID; 2822 } 2823 2824 /** 2825 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or 2826 * {@link #findViewHolderForAdapterPosition(int)} 2827 */ 2828 @Deprecated findViewHolderForPosition(int position)2829 public ViewHolder findViewHolderForPosition(int position) { 2830 return findViewHolderForPosition(position, false); 2831 } 2832 2833 /** 2834 * Return the ViewHolder for the item in the given position of the data set as of the latest 2835 * layout pass. 2836 * <p> 2837 * This method checks only the children of RecyclerView. If the item at the given 2838 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 2839 * <p> 2840 * Note that when Adapter contents change, ViewHolder positions are not updated until the 2841 * next layout calculation. If there are pending adapter updates, the return value of this 2842 * method may not match your adapter contents. You can use 2843 * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. 2844 * 2845 * @param position The position of the item in the data set of the adapter 2846 * @return The ViewHolder at <code>position</code> or null if there is no such item 2847 */ findViewHolderForLayoutPosition(int position)2848 public ViewHolder findViewHolderForLayoutPosition(int position) { 2849 return findViewHolderForPosition(position, false); 2850 } 2851 2852 /** 2853 * Return the ViewHolder for the item in the given position of the data set. Unlike 2854 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending 2855 * adapter changes that may not be reflected to the layout yet. On the other hand, if 2856 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been 2857 * calculated yet, this method will return <code>null</code> since the new positions of views 2858 * are unknown until the layout is calculated. 2859 * <p> 2860 * This method checks only the children of RecyclerView. If the item at the given 2861 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 2862 * 2863 * @param position The position of the item in the data set of the adapter 2864 * @return The ViewHolder at <code>position</code> or null if there is no such item 2865 */ findViewHolderForAdapterPosition(int position)2866 public ViewHolder findViewHolderForAdapterPosition(int position) { 2867 if (mDataSetHasChangedAfterLayout) { 2868 return null; 2869 } 2870 final int childCount = mChildHelper.getUnfilteredChildCount(); 2871 for (int i = 0; i < childCount; i++) { 2872 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2873 if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) { 2874 return holder; 2875 } 2876 } 2877 return null; 2878 } 2879 findViewHolderForPosition(int position, boolean checkNewPosition)2880 ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { 2881 final int childCount = mChildHelper.getUnfilteredChildCount(); 2882 for (int i = 0; i < childCount; i++) { 2883 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2884 if (holder != null && !holder.isRemoved()) { 2885 if (checkNewPosition) { 2886 if (holder.mPosition == position) { 2887 return holder; 2888 } 2889 } else if (holder.getLayoutPosition() == position) { 2890 return holder; 2891 } 2892 } 2893 } 2894 // This method should not query cached views. It creates a problem during adapter updates 2895 // when we are dealing with already laid out views. Also, for the public method, it is more 2896 // reasonable to return null if position is not laid out. 2897 return null; 2898 } 2899 2900 /** 2901 * Return the ViewHolder for the item with the given id. The RecyclerView must 2902 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 2903 * return a non-null value. 2904 * <p> 2905 * This method checks only the children of RecyclerView. If the item with the given 2906 * <code>id</code> is not laid out, it <em>will not</em> create a new one. 2907 * 2908 * @param id The id for the requested item 2909 * @return The ViewHolder with the given <code>id</code> or null if there is no such item 2910 */ findViewHolderForItemId(long id)2911 public ViewHolder findViewHolderForItemId(long id) { 2912 final int childCount = mChildHelper.getUnfilteredChildCount(); 2913 for (int i = 0; i < childCount; i++) { 2914 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2915 if (holder != null && holder.getItemId() == id) { 2916 return holder; 2917 } 2918 } 2919 // this method should not query cached views. They are not children so they 2920 // should not be returned in this public method 2921 return null; 2922 } 2923 2924 /** 2925 * Find the topmost view under the given point. 2926 * 2927 * @param x Horizontal position in pixels to search 2928 * @param y Vertical position in pixels to search 2929 * @return The child view under (x, y) or null if no matching child is found 2930 */ findChildViewUnder(float x, float y)2931 public View findChildViewUnder(float x, float y) { 2932 final int count = mChildHelper.getChildCount(); 2933 for (int i = count - 1; i >= 0; i--) { 2934 final View child = mChildHelper.getChildAt(i); 2935 final float translationX = ViewCompat.getTranslationX(child); 2936 final float translationY = ViewCompat.getTranslationY(child); 2937 if (x >= child.getLeft() + translationX && 2938 x <= child.getRight() + translationX && 2939 y >= child.getTop() + translationY && 2940 y <= child.getBottom() + translationY) { 2941 return child; 2942 } 2943 } 2944 return null; 2945 } 2946 2947 /** 2948 * Offset the bounds of all child views by <code>dy</code> pixels. 2949 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 2950 * 2951 * @param dy Vertical pixel offset to apply to the bounds of all child views 2952 */ offsetChildrenVertical(int dy)2953 public void offsetChildrenVertical(int dy) { 2954 final int childCount = mChildHelper.getChildCount(); 2955 for (int i = 0; i < childCount; i++) { 2956 mChildHelper.getChildAt(i).offsetTopAndBottom(dy); 2957 } 2958 } 2959 2960 /** 2961 * Called when an item view is attached to this RecyclerView. 2962 * 2963 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 2964 * of child views as they become attached. This will be called before a 2965 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 2966 * changes.</p> 2967 * 2968 * @param child Child view that is now attached to this RecyclerView and its associated window 2969 */ onChildAttachedToWindow(View child)2970 public void onChildAttachedToWindow(View child) { 2971 } 2972 2973 /** 2974 * Called when an item view is detached from this RecyclerView. 2975 * 2976 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 2977 * of child views as they become detached. This will be called as a 2978 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 2979 * 2980 * @param child Child view that is now detached from this RecyclerView and its associated window 2981 */ onChildDetachedFromWindow(View child)2982 public void onChildDetachedFromWindow(View child) { 2983 } 2984 2985 /** 2986 * Offset the bounds of all child views by <code>dx</code> pixels. 2987 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 2988 * 2989 * @param dx Horizontal pixel offset to apply to the bounds of all child views 2990 */ offsetChildrenHorizontal(int dx)2991 public void offsetChildrenHorizontal(int dx) { 2992 final int childCount = mChildHelper.getChildCount(); 2993 for (int i = 0; i < childCount; i++) { 2994 mChildHelper.getChildAt(i).offsetLeftAndRight(dx); 2995 } 2996 } 2997 getItemDecorInsetsForChild(View child)2998 Rect getItemDecorInsetsForChild(View child) { 2999 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 3000 if (!lp.mInsetsDirty) { 3001 return lp.mDecorInsets; 3002 } 3003 3004 final Rect insets = lp.mDecorInsets; 3005 insets.set(0, 0, 0, 0); 3006 final int decorCount = mItemDecorations.size(); 3007 for (int i = 0; i < decorCount; i++) { 3008 mTempRect.set(0, 0, 0, 0); 3009 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 3010 insets.left += mTempRect.left; 3011 insets.top += mTempRect.top; 3012 insets.right += mTempRect.right; 3013 insets.bottom += mTempRect.bottom; 3014 } 3015 lp.mInsetsDirty = false; 3016 return insets; 3017 } 3018 3019 private class ViewFlinger implements Runnable { 3020 private int mLastFlingX; 3021 private int mLastFlingY; 3022 private ScrollerCompat mScroller; 3023 private Interpolator mInterpolator = sQuinticInterpolator; 3024 3025 3026 // When set to true, postOnAnimation callbacks are delayed until the run method completes 3027 private boolean mEatRunOnAnimationRequest = false; 3028 3029 // Tracks if postAnimationCallback should be re-attached when it is done 3030 private boolean mReSchedulePostAnimationCallback = false; 3031 ViewFlinger()3032 public ViewFlinger() { 3033 mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator); 3034 } 3035 3036 @Override run()3037 public void run() { 3038 disableRunOnAnimationRequests(); 3039 consumePendingUpdateOperations(); 3040 // keep a local reference so that if it is changed during onAnimation method, it won't 3041 // cause unexpected behaviors 3042 final ScrollerCompat scroller = mScroller; 3043 final SmoothScroller smoothScroller = mLayout.mSmoothScroller; 3044 if (scroller.computeScrollOffset()) { 3045 final int x = scroller.getCurrX(); 3046 final int y = scroller.getCurrY(); 3047 final int dx = x - mLastFlingX; 3048 final int dy = y - mLastFlingY; 3049 int hresult = 0; 3050 int vresult = 0; 3051 mLastFlingX = x; 3052 mLastFlingY = y; 3053 int overscrollX = 0, overscrollY = 0; 3054 if (mAdapter != null) { 3055 eatRequestLayout(); 3056 mRunningLayoutOrScroll = true; 3057 if (dx != 0) { 3058 hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); 3059 overscrollX = dx - hresult; 3060 } 3061 if (dy != 0) { 3062 vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); 3063 overscrollY = dy - vresult; 3064 } 3065 if (supportsChangeAnimations()) { 3066 // Fix up shadow views used by changing animations 3067 int count = mChildHelper.getChildCount(); 3068 for (int i = 0; i < count; i++) { 3069 View view = mChildHelper.getChildAt(i); 3070 ViewHolder holder = getChildViewHolder(view); 3071 if (holder != null && holder.mShadowingHolder != null) { 3072 View shadowingView = holder.mShadowingHolder.itemView; 3073 int left = view.getLeft(); 3074 int top = view.getTop(); 3075 if (left != shadowingView.getLeft() || 3076 top != shadowingView.getTop()) { 3077 shadowingView.layout(left, top, 3078 left + shadowingView.getWidth(), 3079 top + shadowingView.getHeight()); 3080 } 3081 } 3082 } 3083 } 3084 3085 if (smoothScroller != null && !smoothScroller.isPendingInitialRun() && 3086 smoothScroller.isRunning()) { 3087 final int adapterSize = mState.getItemCount(); 3088 if (adapterSize == 0) { 3089 smoothScroller.stop(); 3090 } else if (smoothScroller.getTargetPosition() >= adapterSize) { 3091 smoothScroller.setTargetPosition(adapterSize - 1); 3092 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 3093 } else { 3094 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 3095 } 3096 } 3097 mRunningLayoutOrScroll = false; 3098 resumeRequestLayout(false); 3099 } 3100 if (!mItemDecorations.isEmpty()) { 3101 invalidate(); 3102 } 3103 if (ViewCompat.getOverScrollMode(RecyclerView.this) != 3104 ViewCompat.OVER_SCROLL_NEVER) { 3105 considerReleasingGlowsOnScroll(dx, dy); 3106 } 3107 if (overscrollX != 0 || overscrollY != 0) { 3108 final int vel = (int) scroller.getCurrVelocity(); 3109 3110 int velX = 0; 3111 if (overscrollX != x) { 3112 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; 3113 } 3114 3115 int velY = 0; 3116 if (overscrollY != y) { 3117 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; 3118 } 3119 3120 if (ViewCompat.getOverScrollMode(RecyclerView.this) != 3121 ViewCompat.OVER_SCROLL_NEVER) { 3122 absorbGlows(velX, velY); 3123 } 3124 if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) && 3125 (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { 3126 scroller.abortAnimation(); 3127 } 3128 } 3129 if (hresult != 0 || vresult != 0) { 3130 notifyOnScrolled(hresult, vresult); 3131 } 3132 3133 if (!awakenScrollBars()) { 3134 invalidate(); 3135 } 3136 3137 final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically() 3138 && vresult == dy; 3139 final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally() 3140 && hresult == dx; 3141 final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal 3142 || fullyConsumedVertical; 3143 3144 if (scroller.isFinished() || !fullyConsumedAny) { 3145 setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. 3146 } else { 3147 postOnAnimation(); 3148 } 3149 } 3150 // call this after the onAnimation is complete not to have inconsistent callbacks etc. 3151 if (smoothScroller != null && smoothScroller.isPendingInitialRun()) { 3152 smoothScroller.onAnimation(0, 0); 3153 } 3154 enableRunOnAnimationRequests(); 3155 } 3156 disableRunOnAnimationRequests()3157 private void disableRunOnAnimationRequests() { 3158 mReSchedulePostAnimationCallback = false; 3159 mEatRunOnAnimationRequest = true; 3160 } 3161 enableRunOnAnimationRequests()3162 private void enableRunOnAnimationRequests() { 3163 mEatRunOnAnimationRequest = false; 3164 if (mReSchedulePostAnimationCallback) { 3165 postOnAnimation(); 3166 } 3167 } 3168 postOnAnimation()3169 void postOnAnimation() { 3170 if (mEatRunOnAnimationRequest) { 3171 mReSchedulePostAnimationCallback = true; 3172 } else { 3173 removeCallbacks(this); 3174 ViewCompat.postOnAnimation(RecyclerView.this, this); 3175 } 3176 } 3177 fling(int velocityX, int velocityY)3178 public void fling(int velocityX, int velocityY) { 3179 setScrollState(SCROLL_STATE_SETTLING); 3180 mLastFlingX = mLastFlingY = 0; 3181 mScroller.fling(0, 0, velocityX, velocityY, 3182 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 3183 postOnAnimation(); 3184 } 3185 smoothScrollBy(int dx, int dy)3186 public void smoothScrollBy(int dx, int dy) { 3187 smoothScrollBy(dx, dy, 0, 0); 3188 } 3189 smoothScrollBy(int dx, int dy, int vx, int vy)3190 public void smoothScrollBy(int dx, int dy, int vx, int vy) { 3191 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); 3192 } 3193 distanceInfluenceForSnapDuration(float f)3194 private float distanceInfluenceForSnapDuration(float f) { 3195 f -= 0.5f; // center the values about 0. 3196 f *= 0.3f * Math.PI / 2.0f; 3197 return (float) Math.sin(f); 3198 } 3199 computeScrollDuration(int dx, int dy, int vx, int vy)3200 private int computeScrollDuration(int dx, int dy, int vx, int vy) { 3201 final int absDx = Math.abs(dx); 3202 final int absDy = Math.abs(dy); 3203 final boolean horizontal = absDx > absDy; 3204 final int velocity = (int) Math.sqrt(vx * vx + vy * vy); 3205 final int delta = (int) Math.sqrt(dx * dx + dy * dy); 3206 final int containerSize = horizontal ? getWidth() : getHeight(); 3207 final int halfContainerSize = containerSize / 2; 3208 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); 3209 final float distance = halfContainerSize + halfContainerSize * 3210 distanceInfluenceForSnapDuration(distanceRatio); 3211 3212 final int duration; 3213 if (velocity > 0) { 3214 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 3215 } else { 3216 float absDelta = (float) (horizontal ? absDx : absDy); 3217 duration = (int) (((absDelta / containerSize) + 1) * 300); 3218 } 3219 return Math.min(duration, MAX_SCROLL_DURATION); 3220 } 3221 smoothScrollBy(int dx, int dy, int duration)3222 public void smoothScrollBy(int dx, int dy, int duration) { 3223 smoothScrollBy(dx, dy, duration, sQuinticInterpolator); 3224 } 3225 smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator)3226 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { 3227 if (mInterpolator != interpolator) { 3228 mInterpolator = interpolator; 3229 mScroller = ScrollerCompat.create(getContext(), interpolator); 3230 } 3231 setScrollState(SCROLL_STATE_SETTLING); 3232 mLastFlingX = mLastFlingY = 0; 3233 mScroller.startScroll(0, 0, dx, dy, duration); 3234 postOnAnimation(); 3235 } 3236 stop()3237 public void stop() { 3238 removeCallbacks(this); 3239 mScroller.abortAnimation(); 3240 } 3241 3242 } 3243 notifyOnScrolled(int hresult, int vresult)3244 private void notifyOnScrolled(int hresult, int vresult) { 3245 // dummy values, View's implementation does not use these. 3246 onScrollChanged(0, 0, 0, 0); 3247 if (mScrollListener != null) { 3248 mScrollListener.onScrolled(this, hresult, vresult); 3249 } 3250 } 3251 3252 private class RecyclerViewDataObserver extends AdapterDataObserver { 3253 @Override onChanged()3254 public void onChanged() { 3255 assertNotInLayoutOrScroll(null); 3256 if (mAdapter.hasStableIds()) { 3257 // TODO Determine what actually changed. 3258 // This is more important to implement now since this callback will disable all 3259 // animations because we cannot rely on positions. 3260 mState.mStructureChanged = true; 3261 setDataSetChangedAfterLayout(); 3262 } else { 3263 mState.mStructureChanged = true; 3264 setDataSetChangedAfterLayout(); 3265 } 3266 if (!mAdapterHelper.hasPendingUpdates()) { 3267 requestLayout(); 3268 } 3269 } 3270 3271 @Override onItemRangeChanged(int positionStart, int itemCount)3272 public void onItemRangeChanged(int positionStart, int itemCount) { 3273 assertNotInLayoutOrScroll(null); 3274 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) { 3275 triggerUpdateProcessor(); 3276 } 3277 } 3278 3279 @Override onItemRangeInserted(int positionStart, int itemCount)3280 public void onItemRangeInserted(int positionStart, int itemCount) { 3281 assertNotInLayoutOrScroll(null); 3282 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 3283 triggerUpdateProcessor(); 3284 } 3285 } 3286 3287 @Override onItemRangeRemoved(int positionStart, int itemCount)3288 public void onItemRangeRemoved(int positionStart, int itemCount) { 3289 assertNotInLayoutOrScroll(null); 3290 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 3291 triggerUpdateProcessor(); 3292 } 3293 } 3294 3295 @Override onItemRangeMoved(int fromPosition, int toPosition, int itemCount)3296 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 3297 assertNotInLayoutOrScroll(null); 3298 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 3299 triggerUpdateProcessor(); 3300 } 3301 } 3302 triggerUpdateProcessor()3303 void triggerUpdateProcessor() { 3304 if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { 3305 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); 3306 } else { 3307 mAdapterUpdateDuringMeasure = true; 3308 requestLayout(); 3309 } 3310 } 3311 } 3312 3313 /** 3314 * RecycledViewPool lets you share Views between multiple RecyclerViews. 3315 * <p> 3316 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool 3317 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. 3318 * <p> 3319 * RecyclerView automatically creates a pool for itself if you don't provide one. 3320 * 3321 */ 3322 public static class RecycledViewPool { 3323 private SparseArray<ArrayList<ViewHolder>> mScrap = 3324 new SparseArray<ArrayList<ViewHolder>>(); 3325 private SparseIntArray mMaxScrap = new SparseIntArray(); 3326 private int mAttachCount = 0; 3327 3328 private static final int DEFAULT_MAX_SCRAP = 5; 3329 clear()3330 public void clear() { 3331 mScrap.clear(); 3332 } 3333 setMaxRecycledViews(int viewType, int max)3334 public void setMaxRecycledViews(int viewType, int max) { 3335 mMaxScrap.put(viewType, max); 3336 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 3337 if (scrapHeap != null) { 3338 while (scrapHeap.size() > max) { 3339 scrapHeap.remove(scrapHeap.size() - 1); 3340 } 3341 } 3342 } 3343 getRecycledView(int viewType)3344 public ViewHolder getRecycledView(int viewType) { 3345 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 3346 if (scrapHeap != null && !scrapHeap.isEmpty()) { 3347 final int index = scrapHeap.size() - 1; 3348 final ViewHolder scrap = scrapHeap.get(index); 3349 scrapHeap.remove(index); 3350 return scrap; 3351 } 3352 return null; 3353 } 3354 size()3355 int size() { 3356 int count = 0; 3357 for (int i = 0; i < mScrap.size(); i ++) { 3358 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i); 3359 if (viewHolders != null) { 3360 count += viewHolders.size(); 3361 } 3362 } 3363 return count; 3364 } 3365 putRecycledView(ViewHolder scrap)3366 public void putRecycledView(ViewHolder scrap) { 3367 final int viewType = scrap.getItemViewType(); 3368 final ArrayList scrapHeap = getScrapHeapForType(viewType); 3369 if (mMaxScrap.get(viewType) <= scrapHeap.size()) { 3370 return; 3371 } 3372 scrap.resetInternal(); 3373 scrapHeap.add(scrap); 3374 } 3375 attach(Adapter adapter)3376 void attach(Adapter adapter) { 3377 mAttachCount++; 3378 } 3379 detach()3380 void detach() { 3381 mAttachCount--; 3382 } 3383 3384 3385 /** 3386 * Detaches the old adapter and attaches the new one. 3387 * <p> 3388 * RecycledViewPool will clear its cache if it has only one adapter attached and the new 3389 * adapter uses a different ViewHolder than the oldAdapter. 3390 * 3391 * @param oldAdapter The previous adapter instance. Will be detached. 3392 * @param newAdapter The new adapter instance. Will be attached. 3393 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same 3394 * ViewHolder and view types. 3395 */ onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious)3396 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 3397 boolean compatibleWithPrevious) { 3398 if (oldAdapter != null) { 3399 detach(); 3400 } 3401 if (!compatibleWithPrevious && mAttachCount == 0) { 3402 clear(); 3403 } 3404 if (newAdapter != null) { 3405 attach(newAdapter); 3406 } 3407 } 3408 getScrapHeapForType(int viewType)3409 private ArrayList<ViewHolder> getScrapHeapForType(int viewType) { 3410 ArrayList<ViewHolder> scrap = mScrap.get(viewType); 3411 if (scrap == null) { 3412 scrap = new ArrayList<ViewHolder>(); 3413 mScrap.put(viewType, scrap); 3414 if (mMaxScrap.indexOfKey(viewType) < 0) { 3415 mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); 3416 } 3417 } 3418 return scrap; 3419 } 3420 } 3421 3422 /** 3423 * A Recycler is responsible for managing scrapped or detached item views for reuse. 3424 * 3425 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 3426 * that has been marked for removal or reuse.</p> 3427 * 3428 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 3429 * an adapter's data set representing the data at a given position or item ID. 3430 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 3431 * If not, the view can be quickly reused by the LayoutManager with no further work. 3432 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 3433 * may be repositioned by a LayoutManager without remeasurement.</p> 3434 */ 3435 public final class Recycler { 3436 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>(); 3437 private ArrayList<ViewHolder> mChangedScrap = null; 3438 3439 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 3440 3441 private final List<ViewHolder> 3442 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 3443 3444 private int mViewCacheMax = DEFAULT_CACHE_SIZE; 3445 3446 private RecycledViewPool mRecyclerPool; 3447 3448 private ViewCacheExtension mViewCacheExtension; 3449 3450 private static final int DEFAULT_CACHE_SIZE = 2; 3451 3452 /** 3453 * Clear scrap views out of this recycler. Detached views contained within a 3454 * recycled view pool will remain. 3455 */ clear()3456 public void clear() { 3457 mAttachedScrap.clear(); 3458 recycleAndClearCachedViews(); 3459 } 3460 3461 /** 3462 * Set the maximum number of detached, valid views we should retain for later use. 3463 * 3464 * @param viewCount Number of views to keep before sending views to the shared pool 3465 */ setViewCacheSize(int viewCount)3466 public void setViewCacheSize(int viewCount) { 3467 mViewCacheMax = viewCount; 3468 // first, try the views that can be recycled 3469 for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) { 3470 recycleCachedViewAt(i); 3471 } 3472 } 3473 3474 /** 3475 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. 3476 * 3477 * @return List of ViewHolders in the scrap list. 3478 */ getScrapList()3479 public List<ViewHolder> getScrapList() { 3480 return mUnmodifiableAttachedScrap; 3481 } 3482 3483 /** 3484 * Helper method for getViewForPosition. 3485 * <p> 3486 * Checks whether a given view holder can be used for the provided position. 3487 * 3488 * @param holder ViewHolder 3489 * @return true if ViewHolder matches the provided position, false otherwise 3490 */ validateViewHolderForOffsetPosition(ViewHolder holder)3491 boolean validateViewHolderForOffsetPosition(ViewHolder holder) { 3492 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore 3493 // if it is not removed, verify the type and id. 3494 if (holder.isRemoved()) { 3495 return true; 3496 } 3497 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { 3498 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " 3499 + "adapter position" + holder); 3500 } 3501 if (!mState.isPreLayout()) { 3502 // don't check type if it is pre-layout. 3503 final int type = mAdapter.getItemViewType(holder.mPosition); 3504 if (type != holder.getItemViewType()) { 3505 return false; 3506 } 3507 } 3508 if (mAdapter.hasStableIds()) { 3509 return holder.getItemId() == mAdapter.getItemId(holder.mPosition); 3510 } 3511 return true; 3512 } 3513 3514 /** 3515 * Binds the given View to the position. The View can be a View previously retrieved via 3516 * {@link #getViewForPosition(int)} or created by 3517 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. 3518 * <p> 3519 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} 3520 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who 3521 * wants to handle its own recycling logic. 3522 * <p> 3523 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so 3524 * you don't need to call this method unless you want to bind this View to another position. 3525 * 3526 * @param view The view to update. 3527 * @param position The position of the item to bind to this View. 3528 */ bindViewToPosition(View view, int position)3529 public void bindViewToPosition(View view, int position) { 3530 ViewHolder holder = getChildViewHolderInt(view); 3531 if (holder == null) { 3532 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" 3533 + " pass arbitrary views to this method, they should be created by the " 3534 + "Adapter"); 3535 } 3536 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3537 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 3538 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 3539 + "position " + position + "(offset:" + offsetPosition + ")." 3540 + "state:" + mState.getItemCount()); 3541 } 3542 mAdapter.bindViewHolder(holder, offsetPosition); 3543 attachAccessibilityDelegate(view); 3544 if (mState.isPreLayout()) { 3545 holder.mPreLayoutPosition = position; 3546 } 3547 3548 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 3549 final LayoutParams rvLayoutParams; 3550 if (lp == null) { 3551 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 3552 holder.itemView.setLayoutParams(rvLayoutParams); 3553 } else if (!checkLayoutParams(lp)) { 3554 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 3555 holder.itemView.setLayoutParams(rvLayoutParams); 3556 } else { 3557 rvLayoutParams = (LayoutParams) lp; 3558 } 3559 3560 rvLayoutParams.mInsetsDirty = true; 3561 rvLayoutParams.mViewHolder = holder; 3562 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; 3563 } 3564 3565 /** 3566 * RecyclerView provides artificial position range (item count) in pre-layout state and 3567 * automatically maps these positions to {@link Adapter} positions when 3568 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. 3569 * <p> 3570 * Usually, LayoutManager does not need to worry about this. However, in some cases, your 3571 * LayoutManager may need to call some custom component with item positions in which 3572 * case you need the actual adapter position instead of the pre layout position. You 3573 * can use this method to convert a pre-layout position to adapter (post layout) position. 3574 * <p> 3575 * Note that if the provided position belongs to a deleted ViewHolder, this method will 3576 * return -1. 3577 * <p> 3578 * Calling this method in post-layout state returns the same value back. 3579 * 3580 * @param position The pre-layout position to convert. Must be greater or equal to 0 and 3581 * less than {@link State#getItemCount()}. 3582 */ convertPreLayoutPositionToPostLayout(int position)3583 public int convertPreLayoutPositionToPostLayout(int position) { 3584 if (position < 0 || position >= mState.getItemCount()) { 3585 throw new IndexOutOfBoundsException("invalid position " + position + ". State " 3586 + "item count is " + mState.getItemCount()); 3587 } 3588 if (!mState.isPreLayout()) { 3589 return position; 3590 } 3591 return mAdapterHelper.findPositionOffset(position); 3592 } 3593 3594 /** 3595 * Obtain a view initialized for the given position. 3596 * 3597 * This method should be used by {@link LayoutManager} implementations to obtain 3598 * views to represent data from an {@link Adapter}. 3599 * <p> 3600 * The Recycler may reuse a scrap or detached view from a shared pool if one is 3601 * available for the correct view type. If the adapter has not indicated that the 3602 * data at the given position has changed, the Recycler will attempt to hand back 3603 * a scrap view that was previously initialized for that data without rebinding. 3604 * 3605 * @param position Position to obtain a view for 3606 * @return A view representing the data at <code>position</code> from <code>adapter</code> 3607 */ getViewForPosition(int position)3608 public View getViewForPosition(int position) { 3609 return getViewForPosition(position, false); 3610 } 3611 getViewForPosition(int position, boolean dryRun)3612 View getViewForPosition(int position, boolean dryRun) { 3613 if (position < 0 || position >= mState.getItemCount()) { 3614 throw new IndexOutOfBoundsException("Invalid item position " + position 3615 + "(" + position + "). Item count:" + mState.getItemCount()); 3616 } 3617 boolean fromScrap = false; 3618 ViewHolder holder = null; 3619 // 0) If there is a changed scrap, try to find from there 3620 if (mState.isPreLayout()) { 3621 holder = getChangedScrapViewForPosition(position); 3622 fromScrap = holder != null; 3623 } 3624 // 1) Find from scrap by position 3625 if (holder == null) { 3626 holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); 3627 if (holder != null) { 3628 if (!validateViewHolderForOffsetPosition(holder)) { 3629 // recycle this scrap 3630 if (!dryRun) { 3631 // we would like to recycle this but need to make sure it is not used by 3632 // animation logic etc. 3633 holder.addFlags(ViewHolder.FLAG_INVALID); 3634 if (holder.isScrap()) { 3635 removeDetachedView(holder.itemView, false); 3636 holder.unScrap(); 3637 } else if (holder.wasReturnedFromScrap()) { 3638 holder.clearReturnedFromScrapFlag(); 3639 } 3640 recycleViewHolderInternal(holder); 3641 } 3642 holder = null; 3643 } else { 3644 fromScrap = true; 3645 } 3646 } 3647 } 3648 if (holder == null) { 3649 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3650 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 3651 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 3652 + "position " + position + "(offset:" + offsetPosition + ")." 3653 + "state:" + mState.getItemCount()); 3654 } 3655 3656 final int type = mAdapter.getItemViewType(offsetPosition); 3657 // 2) Find from scrap via stable ids, if exists 3658 if (mAdapter.hasStableIds()) { 3659 holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); 3660 if (holder != null) { 3661 // update position 3662 holder.mPosition = offsetPosition; 3663 fromScrap = true; 3664 } 3665 } 3666 if (holder == null && mViewCacheExtension != null) { 3667 // We are NOT sending the offsetPosition because LayoutManager does not 3668 // know it. 3669 final View view = mViewCacheExtension 3670 .getViewForPositionAndType(this, position, type); 3671 if (view != null) { 3672 holder = getChildViewHolder(view); 3673 if (holder == null) { 3674 throw new IllegalArgumentException("getViewForPositionAndType returned" 3675 + " a view which does not have a ViewHolder"); 3676 } else if (holder.shouldIgnore()) { 3677 throw new IllegalArgumentException("getViewForPositionAndType returned" 3678 + " a view that is ignored. You must call stopIgnoring before" 3679 + " returning this view."); 3680 } 3681 } 3682 } 3683 if (holder == null) { // fallback to recycler 3684 // try recycler. 3685 // Head to the shared pool. 3686 if (DEBUG) { 3687 Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " 3688 + "pool"); 3689 } 3690 holder = getRecycledViewPool() 3691 .getRecycledView(mAdapter.getItemViewType(offsetPosition)); 3692 if (holder != null) { 3693 holder.resetInternal(); 3694 if (FORCE_INVALIDATE_DISPLAY_LIST) { 3695 invalidateDisplayListInt(holder); 3696 } 3697 } 3698 } 3699 if (holder == null) { 3700 holder = mAdapter.createViewHolder(RecyclerView.this, 3701 mAdapter.getItemViewType(offsetPosition)); 3702 if (DEBUG) { 3703 Log.d(TAG, "getViewForPosition created new ViewHolder"); 3704 } 3705 } 3706 } 3707 boolean bound = false; 3708 if (mState.isPreLayout() && holder.isBound()) { 3709 // do not update unless we absolutely have to. 3710 holder.mPreLayoutPosition = position; 3711 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 3712 if (DEBUG && holder.isRemoved()) { 3713 throw new IllegalStateException("Removed holder should be bound and it should" 3714 + " come here only in pre-layout. Holder: " + holder); 3715 } 3716 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3717 mAdapter.bindViewHolder(holder, offsetPosition); 3718 attachAccessibilityDelegate(holder.itemView); 3719 bound = true; 3720 if (mState.isPreLayout()) { 3721 holder.mPreLayoutPosition = position; 3722 } 3723 } 3724 3725 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 3726 final LayoutParams rvLayoutParams; 3727 if (lp == null) { 3728 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 3729 holder.itemView.setLayoutParams(rvLayoutParams); 3730 } else if (!checkLayoutParams(lp)) { 3731 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 3732 holder.itemView.setLayoutParams(rvLayoutParams); 3733 } else { 3734 rvLayoutParams = (LayoutParams) lp; 3735 } 3736 rvLayoutParams.mViewHolder = holder; 3737 rvLayoutParams.mPendingInvalidate = fromScrap && bound; 3738 return holder.itemView; 3739 } 3740 attachAccessibilityDelegate(View itemView)3741 private void attachAccessibilityDelegate(View itemView) { 3742 if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()) { 3743 if (ViewCompat.getImportantForAccessibility(itemView) == 3744 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 3745 ViewCompat.setImportantForAccessibility(itemView, 3746 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 3747 } 3748 if (!ViewCompat.hasAccessibilityDelegate(itemView)) { 3749 ViewCompat.setAccessibilityDelegate(itemView, 3750 mAccessibilityDelegate.getItemDelegate()); 3751 } 3752 } 3753 } 3754 invalidateDisplayListInt(ViewHolder holder)3755 private void invalidateDisplayListInt(ViewHolder holder) { 3756 if (holder.itemView instanceof ViewGroup) { 3757 invalidateDisplayListInt((ViewGroup) holder.itemView, false); 3758 } 3759 } 3760 invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis)3761 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { 3762 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 3763 final View view = viewGroup.getChildAt(i); 3764 if (view instanceof ViewGroup) { 3765 invalidateDisplayListInt((ViewGroup) view, true); 3766 } 3767 } 3768 if (!invalidateThis) { 3769 return; 3770 } 3771 // we need to force it to become invisible 3772 if (viewGroup.getVisibility() == View.INVISIBLE) { 3773 viewGroup.setVisibility(View.VISIBLE); 3774 viewGroup.setVisibility(View.INVISIBLE); 3775 } else { 3776 final int visibility = viewGroup.getVisibility(); 3777 viewGroup.setVisibility(View.INVISIBLE); 3778 viewGroup.setVisibility(visibility); 3779 } 3780 } 3781 3782 /** 3783 * Recycle a detached view. The specified view will be added to a pool of views 3784 * for later rebinding and reuse. 3785 * 3786 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the 3787 * View is scrapped, it will be removed from scrap list.</p> 3788 * 3789 * @param view Removed view for recycling 3790 * @see LayoutManager#removeAndRecycleView(View, Recycler) 3791 */ recycleView(View view)3792 public void recycleView(View view) { 3793 // This public recycle method tries to make view recycle-able since layout manager 3794 // intended to recycle this view (e.g. even if it is in scrap or change cache) 3795 ViewHolder holder = getChildViewHolderInt(view); 3796 if (holder.isTmpDetached()) { 3797 removeDetachedView(view, false); 3798 } 3799 if (holder.isScrap()) { 3800 holder.unScrap(); 3801 } else if (holder.wasReturnedFromScrap()){ 3802 holder.clearReturnedFromScrapFlag(); 3803 } 3804 recycleViewHolderInternal(holder); 3805 } 3806 3807 /** 3808 * Internally, use this method instead of {@link #recycleView(android.view.View)} to 3809 * catch potential bugs. 3810 * @param view 3811 */ recycleViewInternal(View view)3812 void recycleViewInternal(View view) { 3813 recycleViewHolderInternal(getChildViewHolderInt(view)); 3814 } 3815 recycleAndClearCachedViews()3816 void recycleAndClearCachedViews() { 3817 final int count = mCachedViews.size(); 3818 for (int i = count - 1; i >= 0; i--) { 3819 recycleCachedViewAt(i); 3820 } 3821 mCachedViews.clear(); 3822 } 3823 3824 /** 3825 * Recycles a cached view and removes the view from the list. Views are added to cache 3826 * if and only if they are recyclable, so this method does not check it again. 3827 * <p> 3828 * A small exception to this rule is when the view does not have an animator reference 3829 * but transient state is true (due to animations created outside ItemAnimator). In that 3830 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is 3831 * still recyclable since Adapter wants to do so. 3832 * 3833 * @param cachedViewIndex The index of the view in cached views list 3834 */ recycleCachedViewAt(int cachedViewIndex)3835 void recycleCachedViewAt(int cachedViewIndex) { 3836 if (DEBUG) { 3837 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); 3838 } 3839 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 3840 if (DEBUG) { 3841 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); 3842 } 3843 addViewHolderToRecycledViewPool(viewHolder); 3844 mCachedViews.remove(cachedViewIndex); 3845 } 3846 3847 /** 3848 * internal implementation checks if view is scrapped or attached and throws an exception 3849 * if so. 3850 * Public version un-scraps before calling recycle. 3851 */ recycleViewHolderInternal(ViewHolder holder)3852 void recycleViewHolderInternal(ViewHolder holder) { 3853 if (holder.isScrap() || holder.itemView.getParent() != null) { 3854 throw new IllegalArgumentException( 3855 "Scrapped or attached views may not be recycled. isScrap:" 3856 + holder.isScrap() + " isAttached:" 3857 + (holder.itemView.getParent() != null)); 3858 } 3859 3860 if (holder.isTmpDetached()) { 3861 throw new IllegalArgumentException("Tmp detached view should be removed " 3862 + "from RecyclerView before it can be recycled: " + holder); 3863 } 3864 3865 if (holder.shouldIgnore()) { 3866 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" 3867 + " should first call stopIgnoringView(view) before calling recycle."); 3868 } 3869 //noinspection unchecked 3870 final boolean forceRecycle = mAdapter != null 3871 && holder.doesTransientStatePreventRecycling() 3872 && mAdapter.onFailedToRecycleView(holder); 3873 if (forceRecycle || holder.isRecyclable()) { 3874 boolean cached = false; 3875 if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) && 3876 !holder.isChanged()) { 3877 // Retire oldest cached view 3878 final int cachedViewSize = mCachedViews.size(); 3879 if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { 3880 recycleCachedViewAt(0); 3881 } 3882 if (cachedViewSize < mViewCacheMax) { 3883 mCachedViews.add(holder); 3884 cached = true; 3885 } 3886 } 3887 if (!cached) { 3888 addViewHolderToRecycledViewPool(holder); 3889 } 3890 } else if (DEBUG) { 3891 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " 3892 + "re-visit here. We are stil removing it from animation lists"); 3893 } 3894 // even if the holder is not removed, we still call this method so that it is removed 3895 // from view holder lists. 3896 mState.onViewRecycled(holder); 3897 } 3898 addViewHolderToRecycledViewPool(ViewHolder holder)3899 void addViewHolderToRecycledViewPool(ViewHolder holder) { 3900 ViewCompat.setAccessibilityDelegate(holder.itemView, null); 3901 getRecycledViewPool().putRecycledView(holder); 3902 dispatchViewRecycled(holder); 3903 } 3904 3905 /** 3906 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 3907 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 3908 * internal bookkeeping. 3909 */ quickRecycleScrapView(View view)3910 void quickRecycleScrapView(View view) { 3911 final ViewHolder holder = getChildViewHolderInt(view); 3912 holder.mScrapContainer = null; 3913 holder.clearReturnedFromScrapFlag(); 3914 recycleViewHolderInternal(holder); 3915 } 3916 3917 /** 3918 * Mark an attached view as scrap. 3919 * 3920 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 3921 * for rebinding and reuse. Requests for a view for a given position may return a 3922 * reused or rebound scrap view instance.</p> 3923 * 3924 * @param view View to scrap 3925 */ scrapView(View view)3926 void scrapView(View view) { 3927 final ViewHolder holder = getChildViewHolderInt(view); 3928 holder.setScrapContainer(this); 3929 if (!holder.isChanged() || !supportsChangeAnimations()) { 3930 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { 3931 throw new IllegalArgumentException("Called scrap view with an invalid view." 3932 + " Invalid views cannot be reused from scrap, they should rebound from" 3933 + " recycler pool."); 3934 } 3935 mAttachedScrap.add(holder); 3936 } else { 3937 if (mChangedScrap == null) { 3938 mChangedScrap = new ArrayList<ViewHolder>(); 3939 } 3940 mChangedScrap.add(holder); 3941 } 3942 } 3943 3944 /** 3945 * Remove a previously scrapped view from the pool of eligible scrap. 3946 * 3947 * <p>This view will no longer be eligible for reuse until re-scrapped or 3948 * until it is explicitly removed and recycled.</p> 3949 */ unscrapView(ViewHolder holder)3950 void unscrapView(ViewHolder holder) { 3951 if (!holder.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) { 3952 mAttachedScrap.remove(holder); 3953 } else { 3954 mChangedScrap.remove(holder); 3955 } 3956 holder.mScrapContainer = null; 3957 holder.clearReturnedFromScrapFlag(); 3958 } 3959 getScrapCount()3960 int getScrapCount() { 3961 return mAttachedScrap.size(); 3962 } 3963 getScrapViewAt(int index)3964 View getScrapViewAt(int index) { 3965 return mAttachedScrap.get(index).itemView; 3966 } 3967 clearScrap()3968 void clearScrap() { 3969 mAttachedScrap.clear(); 3970 } 3971 getChangedScrapViewForPosition(int position)3972 ViewHolder getChangedScrapViewForPosition(int position) { 3973 // If pre-layout, check the changed scrap for an exact match. 3974 final int changedScrapSize; 3975 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { 3976 return null; 3977 } 3978 // find by position 3979 for (int i = 0; i < changedScrapSize; i++) { 3980 final ViewHolder holder = mChangedScrap.get(i); 3981 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { 3982 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 3983 return holder; 3984 } 3985 } 3986 // find by id 3987 if (mAdapter.hasStableIds()) { 3988 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3989 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { 3990 final long id = mAdapter.getItemId(offsetPosition); 3991 for (int i = 0; i < changedScrapSize; i++) { 3992 final ViewHolder holder = mChangedScrap.get(i); 3993 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { 3994 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 3995 return holder; 3996 } 3997 } 3998 } 3999 } 4000 return null; 4001 } 4002 4003 /** 4004 * Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if 4005 * ViewHolder's type matches the provided type. 4006 * 4007 * @param position Item position 4008 * @param type View type 4009 * @param dryRun Does a dry run, finds the ViewHolder but does not remove 4010 * @return a ViewHolder that can be re-used for this position. 4011 */ getScrapViewForPosition(int position, int type, boolean dryRun)4012 ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { 4013 final int scrapCount = mAttachedScrap.size(); 4014 4015 // Try first for an exact, non-invalid match from scrap. 4016 for (int i = 0; i < scrapCount; i++) { 4017 final ViewHolder holder = mAttachedScrap.get(i); 4018 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position 4019 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { 4020 if (type != INVALID_TYPE && holder.getItemViewType() != type) { 4021 Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + 4022 " wrong view type! (found " + holder.getItemViewType() + 4023 " but expected " + type + ")"); 4024 break; 4025 } 4026 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4027 return holder; 4028 } 4029 } 4030 4031 if (!dryRun) { 4032 View view = mChildHelper.findHiddenNonRemovedView(position, type); 4033 if (view != null) { 4034 // ending the animation should cause it to get recycled before we reuse it 4035 mItemAnimator.endAnimation(getChildViewHolder(view)); 4036 } 4037 } 4038 4039 // Search in our first-level recycled view cache. 4040 final int cacheSize = mCachedViews.size(); 4041 for (int i = 0; i < cacheSize; i++) { 4042 final ViewHolder holder = mCachedViews.get(i); 4043 // invalid view holders may be in cache if adapter has stable ids as they can be 4044 // retrieved via getScrapViewForId 4045 if (!holder.isInvalid() && holder.getLayoutPosition() == position) { 4046 if (!dryRun) { 4047 mCachedViews.remove(i); 4048 } 4049 if (DEBUG) { 4050 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 4051 ") found match in cache: " + holder); 4052 } 4053 return holder; 4054 } 4055 } 4056 return null; 4057 } 4058 getScrapViewForId(long id, int type, boolean dryRun)4059 ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { 4060 // Look in our attached views first 4061 final int count = mAttachedScrap.size(); 4062 for (int i = count - 1; i >= 0; i--) { 4063 final ViewHolder holder = mAttachedScrap.get(i); 4064 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { 4065 if (type == holder.getItemViewType()) { 4066 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4067 if (holder.isRemoved()) { 4068 // this might be valid in two cases: 4069 // > item is removed but we are in pre-layout pass 4070 // >> do nothing. return as is. make sure we don't rebind 4071 // > item is removed then added to another position and we are in 4072 // post layout. 4073 // >> remove removed and invalid flags, add update flag to rebind 4074 // because item was invisible to us and we don't know what happened in 4075 // between. 4076 if (!mState.isPreLayout()) { 4077 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE | 4078 ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); 4079 } 4080 } 4081 return holder; 4082 } else if (!dryRun) { 4083 // Recycle this scrap. Type mismatch. 4084 mAttachedScrap.remove(i); 4085 removeDetachedView(holder.itemView, false); 4086 quickRecycleScrapView(holder.itemView); 4087 } 4088 } 4089 } 4090 4091 // Search the first-level cache 4092 final int cacheSize = mCachedViews.size(); 4093 for (int i = cacheSize - 1; i >= 0; i--) { 4094 final ViewHolder holder = mCachedViews.get(i); 4095 if (holder.getItemId() == id) { 4096 if (type == holder.getItemViewType()) { 4097 if (!dryRun) { 4098 mCachedViews.remove(i); 4099 } 4100 return holder; 4101 } else if (!dryRun) { 4102 recycleCachedViewAt(i); 4103 } 4104 } 4105 } 4106 return null; 4107 } 4108 dispatchViewRecycled(ViewHolder holder)4109 void dispatchViewRecycled(ViewHolder holder) { 4110 if (mRecyclerListener != null) { 4111 mRecyclerListener.onViewRecycled(holder); 4112 } 4113 if (mAdapter != null) { 4114 mAdapter.onViewRecycled(holder); 4115 } 4116 if (mState != null) { 4117 mState.onViewRecycled(holder); 4118 } 4119 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); 4120 } 4121 onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious)4122 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 4123 boolean compatibleWithPrevious) { 4124 clear(); 4125 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); 4126 } 4127 offsetPositionRecordsForMove(int from, int to)4128 void offsetPositionRecordsForMove(int from, int to) { 4129 final int start, end, inBetweenOffset; 4130 if (from < to) { 4131 start = from; 4132 end = to; 4133 inBetweenOffset = -1; 4134 } else { 4135 start = to; 4136 end = from; 4137 inBetweenOffset = 1; 4138 } 4139 final int cachedCount = mCachedViews.size(); 4140 for (int i = 0; i < cachedCount; i++) { 4141 final ViewHolder holder = mCachedViews.get(i); 4142 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 4143 continue; 4144 } 4145 if (holder.mPosition == from) { 4146 holder.offsetPosition(to - from, false); 4147 } else { 4148 holder.offsetPosition(inBetweenOffset, false); 4149 } 4150 if (DEBUG) { 4151 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " + 4152 holder); 4153 } 4154 } 4155 } 4156 offsetPositionRecordsForInsert(int insertedAt, int count)4157 void offsetPositionRecordsForInsert(int insertedAt, int count) { 4158 final int cachedCount = mCachedViews.size(); 4159 for (int i = 0; i < cachedCount; i++) { 4160 final ViewHolder holder = mCachedViews.get(i); 4161 if (holder != null && holder.getLayoutPosition() >= insertedAt) { 4162 if (DEBUG) { 4163 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + 4164 holder + " now at position " + (holder.mPosition + count)); 4165 } 4166 holder.offsetPosition(count, true); 4167 } 4168 } 4169 } 4170 4171 /** 4172 * @param removedFrom Remove start index 4173 * @param count Remove count 4174 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if 4175 * false, they'll be applied before the second layout pass 4176 */ offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout)4177 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { 4178 final int removedEnd = removedFrom + count; 4179 final int cachedCount = mCachedViews.size(); 4180 for (int i = cachedCount - 1; i >= 0; i--) { 4181 final ViewHolder holder = mCachedViews.get(i); 4182 if (holder != null) { 4183 if (holder.getLayoutPosition() >= removedEnd) { 4184 if (DEBUG) { 4185 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + 4186 " holder " + holder + " now at position " + 4187 (holder.mPosition - count)); 4188 } 4189 holder.offsetPosition(-count, applyToPreLayout); 4190 } else if (holder.getLayoutPosition() >= removedFrom) { 4191 // Item for this view was removed. Dump it from the cache. 4192 recycleCachedViewAt(i); 4193 } 4194 } 4195 } 4196 } 4197 setViewCacheExtension(ViewCacheExtension extension)4198 void setViewCacheExtension(ViewCacheExtension extension) { 4199 mViewCacheExtension = extension; 4200 } 4201 setRecycledViewPool(RecycledViewPool pool)4202 void setRecycledViewPool(RecycledViewPool pool) { 4203 if (mRecyclerPool != null) { 4204 mRecyclerPool.detach(); 4205 } 4206 mRecyclerPool = pool; 4207 if (pool != null) { 4208 mRecyclerPool.attach(getAdapter()); 4209 } 4210 } 4211 getRecycledViewPool()4212 RecycledViewPool getRecycledViewPool() { 4213 if (mRecyclerPool == null) { 4214 mRecyclerPool = new RecycledViewPool(); 4215 } 4216 return mRecyclerPool; 4217 } 4218 viewRangeUpdate(int positionStart, int itemCount)4219 void viewRangeUpdate(int positionStart, int itemCount) { 4220 final int positionEnd = positionStart + itemCount; 4221 final int cachedCount = mCachedViews.size(); 4222 for (int i = 0; i < cachedCount; i++) { 4223 final ViewHolder holder = mCachedViews.get(i); 4224 if (holder == null) { 4225 continue; 4226 } 4227 4228 final int pos = holder.getLayoutPosition(); 4229 if (pos >= positionStart && pos < positionEnd) { 4230 holder.addFlags(ViewHolder.FLAG_UPDATE); 4231 // cached views should not be flagged as changed because this will cause them 4232 // to animate when they are returned from cache. 4233 } 4234 } 4235 } 4236 setAdapterPositionsAsUnknown()4237 void setAdapterPositionsAsUnknown() { 4238 final int cachedCount = mCachedViews.size(); 4239 for (int i = 0; i < cachedCount; i++) { 4240 final ViewHolder holder = mCachedViews.get(i); 4241 if (holder != null) { 4242 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 4243 } 4244 } 4245 } 4246 markKnownViewsInvalid()4247 void markKnownViewsInvalid() { 4248 if (mAdapter != null && mAdapter.hasStableIds()) { 4249 final int cachedCount = mCachedViews.size(); 4250 for (int i = 0; i < cachedCount; i++) { 4251 final ViewHolder holder = mCachedViews.get(i); 4252 if (holder != null) { 4253 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 4254 } 4255 } 4256 } else { 4257 // we cannot re-use cached views in this case. Recycle them all 4258 recycleAndClearCachedViews(); 4259 } 4260 } 4261 clearOldPositions()4262 void clearOldPositions() { 4263 final int cachedCount = mCachedViews.size(); 4264 for (int i = 0; i < cachedCount; i++) { 4265 final ViewHolder holder = mCachedViews.get(i); 4266 holder.clearOldPosition(); 4267 } 4268 final int scrapCount = mAttachedScrap.size(); 4269 for (int i = 0; i < scrapCount; i++) { 4270 mAttachedScrap.get(i).clearOldPosition(); 4271 } 4272 if (mChangedScrap != null) { 4273 final int changedScrapCount = mChangedScrap.size(); 4274 for (int i = 0; i < changedScrapCount; i++) { 4275 mChangedScrap.get(i).clearOldPosition(); 4276 } 4277 } 4278 } 4279 markItemDecorInsetsDirty()4280 void markItemDecorInsetsDirty() { 4281 final int cachedCount = mCachedViews.size(); 4282 for (int i = 0; i < cachedCount; i++) { 4283 final ViewHolder holder = mCachedViews.get(i); 4284 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); 4285 if (layoutParams != null) { 4286 layoutParams.mInsetsDirty = true; 4287 } 4288 } 4289 } 4290 } 4291 4292 /** 4293 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can 4294 * ben controlled by the developer. 4295 * <p> 4296 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and 4297 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will 4298 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking 4299 * {@link RecycledViewPool}. 4300 * <p> 4301 * Note that, Recycler never sends Views to this method to be cached. It is developers 4302 * responsibility to decide whether they want to keep their Views in this custom cache or let 4303 * the default recycling policy handle it. 4304 */ 4305 public abstract static class ViewCacheExtension { 4306 4307 /** 4308 * Returns a View that can be binded to the given Adapter position. 4309 * <p> 4310 * This method should <b>not</b> create a new View. Instead, it is expected to return 4311 * an already created View that can be re-used for the given type and position. 4312 * If the View is marked as ignored, it should first call 4313 * {@link LayoutManager#stopIgnoringView(View)} before returning the View. 4314 * <p> 4315 * RecyclerView will re-bind the returned View to the position if necessary. 4316 * 4317 * @param recycler The Recycler that can be used to bind the View 4318 * @param position The adapter position 4319 * @param type The type of the View, defined by adapter 4320 * @return A View that is bound to the given position or NULL if there is no View to re-use 4321 * @see LayoutManager#ignoreView(View) 4322 */ getViewForPositionAndType(Recycler recycler, int position, int type)4323 abstract public View getViewForPositionAndType(Recycler recycler, int position, int type); 4324 } 4325 4326 /** 4327 * Base class for an Adapter 4328 * 4329 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 4330 * within a {@link RecyclerView}.</p> 4331 */ 4332 public static abstract class Adapter<VH extends ViewHolder> { 4333 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 4334 private boolean mHasStableIds = false; 4335 4336 /** 4337 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent 4338 * an item. 4339 * <p> 4340 * This new ViewHolder should be constructed with a new View that can represent the items 4341 * of the given type. You can either create a new View manually or inflate it from an XML 4342 * layout file. 4343 * <p> 4344 * The new ViewHolder will be used to display items of the adapter using 4345 * {@link #onBindViewHolder(ViewHolder, int)}. Since it will be re-used to display different 4346 * items in the data set, it is a good idea to cache references to sub views of the View to 4347 * avoid unnecessary {@link View#findViewById(int)} calls. 4348 * 4349 * @param parent The ViewGroup into which the new View will be added after it is bound to 4350 * an adapter position. 4351 * @param viewType The view type of the new View. 4352 * 4353 * @return A new ViewHolder that holds a View of the given view type. 4354 * @see #getItemViewType(int) 4355 * @see #onBindViewHolder(ViewHolder, int) 4356 */ onCreateViewHolder(ViewGroup parent, int viewType)4357 public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); 4358 4359 /** 4360 * Called by RecyclerView to display the data at the specified position. This method 4361 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at 4362 * the given position. 4363 * <p> 4364 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this 4365 * method again if the position of the item changes in the data set unless the item itself 4366 * is invalidated or the new position cannot be determined. For this reason, you should only 4367 * use the <code>position</code> parameter while acquiring the related data item inside this 4368 * method and should not keep a copy of it. If you need the position of an item later on 4369 * (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will have 4370 * the updated adapter position. 4371 * 4372 * @param holder The ViewHolder which should be updated to represent the contents of the 4373 * item at the given position in the data set. 4374 * @param position The position of the item within the adapter's data set. 4375 */ onBindViewHolder(VH holder, int position)4376 public abstract void onBindViewHolder(VH holder, int position); 4377 4378 /** 4379 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new 4380 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. 4381 * 4382 * @see #onCreateViewHolder(ViewGroup, int) 4383 */ createViewHolder(ViewGroup parent, int viewType)4384 public final VH createViewHolder(ViewGroup parent, int viewType) { 4385 final VH holder = onCreateViewHolder(parent, viewType); 4386 holder.mItemViewType = viewType; 4387 return holder; 4388 } 4389 4390 /** 4391 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the 4392 * {@link ViewHolder} contents with the item at the given position and also sets up some 4393 * private fields to be used by RecyclerView. 4394 * 4395 * @see #onBindViewHolder(ViewHolder, int) 4396 */ bindViewHolder(VH holder, int position)4397 public final void bindViewHolder(VH holder, int position) { 4398 holder.mPosition = position; 4399 if (hasStableIds()) { 4400 holder.mItemId = getItemId(position); 4401 } 4402 onBindViewHolder(holder, position); 4403 holder.setFlags(ViewHolder.FLAG_BOUND, 4404 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID 4405 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 4406 } 4407 4408 /** 4409 * Return the view type of the item at <code>position</code> for the purposes 4410 * of view recycling. 4411 * 4412 * <p>The default implementation of this method returns 0, making the assumption of 4413 * a single view type for the adapter. Unlike ListView adapters, types need not 4414 * be contiguous. Consider using id resources to uniquely identify item view types. 4415 * 4416 * @param position position to query 4417 * @return integer value identifying the type of the view needed to represent the item at 4418 * <code>position</code>. Type codes need not be contiguous. 4419 */ getItemViewType(int position)4420 public int getItemViewType(int position) { 4421 return 0; 4422 } 4423 4424 /** 4425 * Indicates whether each item in the data set can be represented with a unique identifier 4426 * of type {@link java.lang.Long}. 4427 * 4428 * @param hasStableIds Whether items in data set have unique identifiers or not. 4429 * @see #hasStableIds() 4430 * @see #getItemId(int) 4431 */ setHasStableIds(boolean hasStableIds)4432 public void setHasStableIds(boolean hasStableIds) { 4433 if (hasObservers()) { 4434 throw new IllegalStateException("Cannot change whether this adapter has " + 4435 "stable IDs while the adapter has registered observers."); 4436 } 4437 mHasStableIds = hasStableIds; 4438 } 4439 4440 /** 4441 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 4442 * would return false this method should return {@link #NO_ID}. The default implementation 4443 * of this method returns {@link #NO_ID}. 4444 * 4445 * @param position Adapter position to query 4446 * @return the stable ID of the item at position 4447 */ getItemId(int position)4448 public long getItemId(int position) { 4449 return NO_ID; 4450 } 4451 4452 /** 4453 * Returns the total number of items in the data set hold by the adapter. 4454 * 4455 * @return The total number of items in this adapter. 4456 */ getItemCount()4457 public abstract int getItemCount(); 4458 4459 /** 4460 * Returns true if this adapter publishes a unique <code>long</code> value that can 4461 * act as a key for the item at a given position in the data set. If that item is relocated 4462 * in the data set, the ID returned for that item should be the same. 4463 * 4464 * @return true if this adapter's items have stable IDs 4465 */ hasStableIds()4466 public final boolean hasStableIds() { 4467 return mHasStableIds; 4468 } 4469 4470 /** 4471 * Called when a view created by this adapter has been recycled. 4472 * 4473 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 4474 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 4475 * fallen out of visibility or a set of cached views represented by views still 4476 * attached to the parent RecyclerView. If an item view has large or expensive data 4477 * bound to it such as large bitmaps, this may be a good place to release those 4478 * resources.</p> 4479 * 4480 * @param holder The ViewHolder for the view being recycled 4481 */ onViewRecycled(VH holder)4482 public void onViewRecycled(VH holder) { 4483 } 4484 4485 /** 4486 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled 4487 * due to its transient state. Upon receiving this callback, Adapter can clear the 4488 * animation(s) that effect the View's transient state and return <code>true</code> so that 4489 * the View can be recycled. Keep in mind that the View in question is already removed from 4490 * the RecyclerView. 4491 * <p> 4492 * In some cases, it is acceptable to recycle a View although it has transient state. Most 4493 * of the time, this is a case where the transient state will be cleared in 4494 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. 4495 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return 4496 * value of this method to decide whether the View should be recycled or not. 4497 * <p> 4498 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you 4499 * should never receive this callback because RecyclerView keeps those Views as children 4500 * until their animations are complete. This callback is useful when children of the item 4501 * views create animations which may not be easy to implement using an {@link ItemAnimator}. 4502 * <p> 4503 * You should <em>never</em> fix this issue by calling 4504 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called 4505 * <code>holder.itemView.setHasTransientState(true);</code>. Each 4506 * <code>View.setHasTransientState(true)</code> call must be matched by a 4507 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View 4508 * may become inconsistent. You should always prefer to end or cancel animations that are 4509 * triggering the transient state instead of handling it manually. 4510 * 4511 * @param holder The ViewHolder containing the View that could not be recycled due to its 4512 * transient state. 4513 * @return True if the View should be recycled, false otherwise. Note that if this method 4514 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of 4515 * the View and recycle it regardless. If this method returns <code>false</code>, 4516 * RecyclerView will check the View's transient state again before giving a final decision. 4517 * Default implementation returns false. 4518 */ onFailedToRecycleView(VH holder)4519 public boolean onFailedToRecycleView(VH holder) { 4520 return false; 4521 } 4522 4523 /** 4524 * Called when a view created by this adapter has been attached to a window. 4525 * 4526 * <p>This can be used as a reasonable signal that the view is about to be seen 4527 * by the user. If the adapter previously freed any resources in 4528 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 4529 * those resources should be restored here.</p> 4530 * 4531 * @param holder Holder of the view being attached 4532 */ onViewAttachedToWindow(VH holder)4533 public void onViewAttachedToWindow(VH holder) { 4534 } 4535 4536 /** 4537 * Called when a view created by this adapter has been detached from its window. 4538 * 4539 * <p>Becoming detached from the window is not necessarily a permanent condition; 4540 * the consumer of an Adapter's views may choose to cache views offscreen while they 4541 * are not visible, attaching an detaching them as appropriate.</p> 4542 * 4543 * @param holder Holder of the view being detached 4544 */ onViewDetachedFromWindow(VH holder)4545 public void onViewDetachedFromWindow(VH holder) { 4546 } 4547 4548 /** 4549 * Returns true if one or more observers are attached to this adapter. 4550 * 4551 * @return true if this adapter has observers 4552 */ hasObservers()4553 public final boolean hasObservers() { 4554 return mObservable.hasObservers(); 4555 } 4556 4557 /** 4558 * Register a new observer to listen for data changes. 4559 * 4560 * <p>The adapter may publish a variety of events describing specific changes. 4561 * Not all adapters may support all change types and some may fall back to a generic 4562 * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() 4563 * "something changed"} event if more specific data is not available.</p> 4564 * 4565 * <p>Components registering observers with an adapter are responsible for 4566 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 4567 * unregistering} those observers when finished.</p> 4568 * 4569 * @param observer Observer to register 4570 * 4571 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 4572 */ registerAdapterDataObserver(AdapterDataObserver observer)4573 public void registerAdapterDataObserver(AdapterDataObserver observer) { 4574 mObservable.registerObserver(observer); 4575 } 4576 4577 /** 4578 * Unregister an observer currently listening for data changes. 4579 * 4580 * <p>The unregistered observer will no longer receive events about changes 4581 * to the adapter.</p> 4582 * 4583 * @param observer Observer to unregister 4584 * 4585 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) 4586 */ unregisterAdapterDataObserver(AdapterDataObserver observer)4587 public void unregisterAdapterDataObserver(AdapterDataObserver observer) { 4588 mObservable.unregisterObserver(observer); 4589 } 4590 4591 /** 4592 * Called by RecyclerView when it starts observing this Adapter. 4593 * <p> 4594 * Keep in mind that same adapter may be observed by multiple RecyclerViews. 4595 * 4596 * @param recyclerView The RecyclerView instance which started observing this adapter. 4597 * @see #onDetachedFromRecyclerView(RecyclerView) 4598 */ onAttachedToRecyclerView(RecyclerView recyclerView)4599 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 4600 } 4601 4602 /** 4603 * Called by RecyclerView when it stops observing this Adapter. 4604 * 4605 * @param recyclerView The RecyclerView instance which stopped observing this adapter. 4606 * @see #onAttachedToRecyclerView(RecyclerView) 4607 */ onDetachedFromRecyclerView(RecyclerView recyclerView)4608 public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 4609 } 4610 4611 /** 4612 * Notify any registered observers that the data set has changed. 4613 * 4614 * <p>There are two different classes of data change events, item changes and structural 4615 * changes. Item changes are when a single item has its data updated but no positional 4616 * changes have occurred. Structural changes are when items are inserted, removed or moved 4617 * within the data set.</p> 4618 * 4619 * <p>This event does not specify what about the data set has changed, forcing 4620 * any observers to assume that all existing items and structure may no longer be valid. 4621 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 4622 * 4623 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 4624 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 4625 * this method is used. This can help for the purposes of animation and visual 4626 * object persistence but individual item views will still need to be rebound 4627 * and relaid out.</p> 4628 * 4629 * <p>If you are writing an adapter it will always be more efficient to use the more 4630 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 4631 * as a last resort.</p> 4632 * 4633 * @see #notifyItemChanged(int) 4634 * @see #notifyItemInserted(int) 4635 * @see #notifyItemRemoved(int) 4636 * @see #notifyItemRangeChanged(int, int) 4637 * @see #notifyItemRangeInserted(int, int) 4638 * @see #notifyItemRangeRemoved(int, int) 4639 */ notifyDataSetChanged()4640 public final void notifyDataSetChanged() { 4641 mObservable.notifyChanged(); 4642 } 4643 4644 /** 4645 * Notify any registered observers that the item at <code>position</code> has changed. 4646 * 4647 * <p>This is an item change event, not a structural change event. It indicates that any 4648 * reflection of the data at <code>position</code> is out of date and should be updated. 4649 * The item at <code>position</code> retains the same identity.</p> 4650 * 4651 * @param position Position of the item that has changed 4652 * 4653 * @see #notifyItemRangeChanged(int, int) 4654 */ notifyItemChanged(int position)4655 public final void notifyItemChanged(int position) { 4656 mObservable.notifyItemRangeChanged(position, 1); 4657 } 4658 4659 /** 4660 * Notify any registered observers that the <code>itemCount</code> items starting at 4661 * position <code>positionStart</code> have changed. 4662 * 4663 * <p>This is an item change event, not a structural change event. It indicates that 4664 * any reflection of the data in the given position range is out of date and should 4665 * be updated. The items in the given range retain the same identity.</p> 4666 * 4667 * @param positionStart Position of the first item that has changed 4668 * @param itemCount Number of items that have changed 4669 * 4670 * @see #notifyItemChanged(int) 4671 */ notifyItemRangeChanged(int positionStart, int itemCount)4672 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 4673 mObservable.notifyItemRangeChanged(positionStart, itemCount); 4674 } 4675 4676 /** 4677 * Notify any registered observers that the item reflected at <code>position</code> 4678 * has been newly inserted. The item previously at <code>position</code> is now at 4679 * position <code>position + 1</code>. 4680 * 4681 * <p>This is a structural change event. Representations of other existing items in the 4682 * data set are still considered up to date and will not be rebound, though their 4683 * positions may be altered.</p> 4684 * 4685 * @param position Position of the newly inserted item in the data set 4686 * 4687 * @see #notifyItemRangeInserted(int, int) 4688 */ notifyItemInserted(int position)4689 public final void notifyItemInserted(int position) { 4690 mObservable.notifyItemRangeInserted(position, 1); 4691 } 4692 4693 /** 4694 * Notify any registered observers that the item reflected at <code>fromPosition</code> 4695 * has been moved to <code>toPosition</code>. 4696 * 4697 * <p>This is a structural change event. Representations of other existing items in the 4698 * data set are still considered up to date and will not be rebound, though their 4699 * positions may be altered.</p> 4700 * 4701 * @param fromPosition Previous position of the item. 4702 * @param toPosition New position of the item. 4703 */ notifyItemMoved(int fromPosition, int toPosition)4704 public final void notifyItemMoved(int fromPosition, int toPosition) { 4705 mObservable.notifyItemMoved(fromPosition, toPosition); 4706 } 4707 4708 /** 4709 * Notify any registered observers that the currently reflected <code>itemCount</code> 4710 * items starting at <code>positionStart</code> have been newly inserted. The items 4711 * previously located at <code>positionStart</code> and beyond can now be found starting 4712 * at position <code>positionStart + itemCount</code>. 4713 * 4714 * <p>This is a structural change event. Representations of other existing items in the 4715 * data set are still considered up to date and will not be rebound, though their positions 4716 * may be altered.</p> 4717 * 4718 * @param positionStart Position of the first item that was inserted 4719 * @param itemCount Number of items inserted 4720 * 4721 * @see #notifyItemInserted(int) 4722 */ notifyItemRangeInserted(int positionStart, int itemCount)4723 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 4724 mObservable.notifyItemRangeInserted(positionStart, itemCount); 4725 } 4726 4727 /** 4728 * Notify any registered observers that the item previously located at <code>position</code> 4729 * has been removed from the data set. The items previously located at and after 4730 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 4731 * 4732 * <p>This is a structural change event. Representations of other existing items in the 4733 * data set are still considered up to date and will not be rebound, though their positions 4734 * may be altered.</p> 4735 * 4736 * @param position Position of the item that has now been removed 4737 * 4738 * @see #notifyItemRangeRemoved(int, int) 4739 */ notifyItemRemoved(int position)4740 public final void notifyItemRemoved(int position) { 4741 mObservable.notifyItemRangeRemoved(position, 1); 4742 } 4743 4744 /** 4745 * Notify any registered observers that the <code>itemCount</code> items previously 4746 * located at <code>positionStart</code> have been removed from the data set. The items 4747 * previously located at and after <code>positionStart + itemCount</code> may now be found 4748 * at <code>oldPosition - itemCount</code>. 4749 * 4750 * <p>This is a structural change event. Representations of other existing items in the data 4751 * set are still considered up to date and will not be rebound, though their positions 4752 * may be altered.</p> 4753 * 4754 * @param positionStart Previous position of the first item that was removed 4755 * @param itemCount Number of items removed from the data set 4756 */ notifyItemRangeRemoved(int positionStart, int itemCount)4757 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 4758 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 4759 } 4760 } 4761 dispatchChildDetached(View child)4762 private void dispatchChildDetached(View child) { 4763 if (mAdapter != null) { 4764 mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); 4765 } 4766 onChildDetachedFromWindow(child); 4767 } 4768 dispatchChildAttached(View child)4769 private void dispatchChildAttached(View child) { 4770 if (mAdapter != null) { 4771 mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child)); 4772 } 4773 onChildAttachedToWindow(child); 4774 } 4775 4776 /** 4777 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 4778 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 4779 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 4780 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 4781 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 4782 * layout managers are provided for general use. 4783 */ 4784 public static abstract class LayoutManager { 4785 ChildHelper mChildHelper; 4786 RecyclerView mRecyclerView; 4787 4788 @Nullable 4789 SmoothScroller mSmoothScroller; 4790 4791 private boolean mRequestedSimpleAnimations = false; 4792 setRecyclerView(RecyclerView recyclerView)4793 void setRecyclerView(RecyclerView recyclerView) { 4794 if (recyclerView == null) { 4795 mRecyclerView = null; 4796 mChildHelper = null; 4797 } else { 4798 mRecyclerView = recyclerView; 4799 mChildHelper = recyclerView.mChildHelper; 4800 } 4801 4802 } 4803 4804 /** 4805 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView 4806 */ requestLayout()4807 public void requestLayout() { 4808 if(mRecyclerView != null) { 4809 mRecyclerView.requestLayout(); 4810 } 4811 } 4812 4813 /** 4814 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 4815 * {@link IllegalStateException} if it <b>is not</b>. 4816 * 4817 * @param message The message for the exception. Can be null. 4818 * @see #assertNotInLayoutOrScroll(String) 4819 */ assertInLayoutOrScroll(String message)4820 public void assertInLayoutOrScroll(String message) { 4821 if (mRecyclerView != null) { 4822 mRecyclerView.assertInLayoutOrScroll(message); 4823 } 4824 } 4825 4826 /** 4827 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 4828 * {@link IllegalStateException} if it <b>is</b>. 4829 * 4830 * @param message The message for the exception. Can be null. 4831 * @see #assertInLayoutOrScroll(String) 4832 */ assertNotInLayoutOrScroll(String message)4833 public void assertNotInLayoutOrScroll(String message) { 4834 if (mRecyclerView != null) { 4835 mRecyclerView.assertNotInLayoutOrScroll(message); 4836 } 4837 } 4838 4839 /** 4840 * Returns whether this LayoutManager supports automatic item animations. 4841 * A LayoutManager wishing to support item animations should obey certain 4842 * rules as outlined in {@link #onLayoutChildren(Recycler, State)}. 4843 * The default return value is <code>false</code>, so subclasses of LayoutManager 4844 * will not get predictive item animations by default. 4845 * 4846 * <p>Whether item animations are enabled in a RecyclerView is determined both 4847 * by the return value from this method and the 4848 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the 4849 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this 4850 * method returns false, then simple item animations will be enabled, in which 4851 * views that are moving onto or off of the screen are simply faded in/out. If 4852 * the RecyclerView has a non-null ItemAnimator and this method returns true, 4853 * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to 4854 * setup up the information needed to more intelligently predict where appearing 4855 * and disappearing views should be animated from/to.</p> 4856 * 4857 * @return true if predictive item animations should be enabled, false otherwise 4858 */ supportsPredictiveItemAnimations()4859 public boolean supportsPredictiveItemAnimations() { 4860 return false; 4861 } 4862 4863 /** 4864 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 4865 * is attached to a window. 4866 * 4867 * <p>Subclass implementations should always call through to the superclass implementation. 4868 * </p> 4869 * 4870 * @param view The RecyclerView this LayoutManager is bound to 4871 */ onAttachedToWindow(RecyclerView view)4872 public void onAttachedToWindow(RecyclerView view) { 4873 } 4874 4875 /** 4876 * @deprecated 4877 * override {@link #onDetachedFromWindow(RecyclerView, Recycler)} 4878 */ 4879 @Deprecated onDetachedFromWindow(RecyclerView view)4880 public void onDetachedFromWindow(RecyclerView view) { 4881 4882 } 4883 4884 /** 4885 * Called when this LayoutManager is detached from its parent RecyclerView or when 4886 * its parent RecyclerView is detached from its window. 4887 * 4888 * <p>Subclass implementations should always call through to the superclass implementation. 4889 * </p> 4890 * 4891 * @param view The RecyclerView this LayoutManager is bound to 4892 * @param recycler The recycler to use if you prefer to recycle your children instead of 4893 * keeping them around. 4894 */ onDetachedFromWindow(RecyclerView view, Recycler recycler)4895 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { 4896 onDetachedFromWindow(view); 4897 } 4898 4899 /** 4900 * Check if the RecyclerView is configured to clip child views to its padding. 4901 * 4902 * @return true if this RecyclerView clips children to its padding, false otherwise 4903 */ getClipToPadding()4904 public boolean getClipToPadding() { 4905 return mRecyclerView != null && mRecyclerView.mClipToPadding; 4906 } 4907 4908 /** 4909 * Lay out all relevant child views from the given adapter. 4910 * 4911 * The LayoutManager is in charge of the behavior of item animations. By default, 4912 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple 4913 * item animations are enabled. This means that add/remove operations on the 4914 * adapter will result in animations to add new or appearing items, removed or 4915 * disappearing items, and moved items. If a LayoutManager returns false from 4916 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a 4917 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the 4918 * RecyclerView will have enough information to run those animations in a simple 4919 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will 4920 * simple fade views in and out, whether they are actuall added/removed or whether 4921 * they are moved on or off the screen due to other add/remove operations. 4922 * 4923 * <p>A LayoutManager wanting a better item animation experience, where items can be 4924 * animated onto and off of the screen according to where the items exist when they 4925 * are not on screen, then the LayoutManager should return true from 4926 * {@link #supportsPredictiveItemAnimations()} and add additional logic to 4927 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations 4928 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; 4929 * once as a "pre" layout step to determine where items would have been prior to 4930 * a real layout, and again to do the "real" layout. In the pre-layout phase, 4931 * items will remember their pre-layout positions to allow them to be laid out 4932 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will 4933 * be returned from the scrap to help determine correct placement of other items. 4934 * These removed items should not be added to the child list, but should be used 4935 * to help calculate correct positioning of other views, including views that 4936 * were not previously onscreen (referred to as APPEARING views), but whose 4937 * pre-layout offscreen position can be determined given the extra 4938 * information about the pre-layout removed views.</p> 4939 * 4940 * <p>The second layout pass is the real layout in which only non-removed views 4941 * will be used. The only additional requirement during this pass is, if 4942 * {@link #supportsPredictiveItemAnimations()} returns true, to note which 4943 * views exist in the child list prior to layout and which are not there after 4944 * layout (referred to as DISAPPEARING views), and to position/layout those views 4945 * appropriately, without regard to the actual bounds of the RecyclerView. This allows 4946 * the animation system to know the location to which to animate these disappearing 4947 * views.</p> 4948 * 4949 * <p>The default LayoutManager implementations for RecyclerView handle all of these 4950 * requirements for animations already. Clients of RecyclerView can either use one 4951 * of these layout managers directly or look at their implementations of 4952 * onLayoutChildren() to see how they account for the APPEARING and 4953 * DISAPPEARING views.</p> 4954 * 4955 * @param recycler Recycler to use for fetching potentially cached views for a 4956 * position 4957 * @param state Transient state of RecyclerView 4958 */ onLayoutChildren(Recycler recycler, State state)4959 public void onLayoutChildren(Recycler recycler, State state) { 4960 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); 4961 } 4962 4963 /** 4964 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 4965 * 4966 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 4967 * to store extra information specific to the layout. Client code should subclass 4968 * {@link RecyclerView.LayoutParams} for this purpose.</p> 4969 * 4970 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 4971 * you must also override 4972 * {@link #checkLayoutParams(LayoutParams)}, 4973 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 4974 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 4975 * 4976 * @return A new LayoutParams for a child view 4977 */ generateDefaultLayoutParams()4978 public abstract LayoutParams generateDefaultLayoutParams(); 4979 4980 /** 4981 * Determines the validity of the supplied LayoutParams object. 4982 * 4983 * <p>This should check to make sure that the object is of the correct type 4984 * and all values are within acceptable ranges. The default implementation 4985 * returns <code>true</code> for non-null params.</p> 4986 * 4987 * @param lp LayoutParams object to check 4988 * @return true if this LayoutParams object is valid, false otherwise 4989 */ checkLayoutParams(LayoutParams lp)4990 public boolean checkLayoutParams(LayoutParams lp) { 4991 return lp != null; 4992 } 4993 4994 /** 4995 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 4996 * values from the supplied LayoutParams object if possible. 4997 * 4998 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 4999 * you must also override 5000 * {@link #checkLayoutParams(LayoutParams)}, 5001 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 5002 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 5003 * 5004 * @param lp Source LayoutParams object to copy values from 5005 * @return a new LayoutParams object 5006 */ generateLayoutParams(ViewGroup.LayoutParams lp)5007 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 5008 if (lp instanceof LayoutParams) { 5009 return new LayoutParams((LayoutParams) lp); 5010 } else if (lp instanceof MarginLayoutParams) { 5011 return new LayoutParams((MarginLayoutParams) lp); 5012 } else { 5013 return new LayoutParams(lp); 5014 } 5015 } 5016 5017 /** 5018 * Create a LayoutParams object suitable for this LayoutManager from 5019 * an inflated layout resource. 5020 * 5021 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 5022 * you must also override 5023 * {@link #checkLayoutParams(LayoutParams)}, 5024 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 5025 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 5026 * 5027 * @param c Context for obtaining styled attributes 5028 * @param attrs AttributeSet describing the supplied arguments 5029 * @return a new LayoutParams object 5030 */ generateLayoutParams(Context c, AttributeSet attrs)5031 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 5032 return new LayoutParams(c, attrs); 5033 } 5034 5035 /** 5036 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 5037 * The default implementation does nothing and returns 0. 5038 * 5039 * @param dx distance to scroll by in pixels. X increases as scroll position 5040 * approaches the right. 5041 * @param recycler Recycler to use for fetching potentially cached views for a 5042 * position 5043 * @param state Transient state of RecyclerView 5044 * @return The actual distance scrolled. The return value will be negative if dx was 5045 * negative and scrolling proceeeded in that direction. 5046 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 5047 */ scrollHorizontallyBy(int dx, Recycler recycler, State state)5048 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { 5049 return 0; 5050 } 5051 5052 /** 5053 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 5054 * The default implementation does nothing and returns 0. 5055 * 5056 * @param dy distance to scroll in pixels. Y increases as scroll position 5057 * approaches the bottom. 5058 * @param recycler Recycler to use for fetching potentially cached views for a 5059 * position 5060 * @param state Transient state of RecyclerView 5061 * @return The actual distance scrolled. The return value will be negative if dy was 5062 * negative and scrolling proceeeded in that direction. 5063 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 5064 */ scrollVerticallyBy(int dy, Recycler recycler, State state)5065 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { 5066 return 0; 5067 } 5068 5069 /** 5070 * Query if horizontal scrolling is currently supported. The default implementation 5071 * returns false. 5072 * 5073 * @return True if this LayoutManager can scroll the current contents horizontally 5074 */ canScrollHorizontally()5075 public boolean canScrollHorizontally() { 5076 return false; 5077 } 5078 5079 /** 5080 * Query if vertical scrolling is currently supported. The default implementation 5081 * returns false. 5082 * 5083 * @return True if this LayoutManager can scroll the current contents vertically 5084 */ canScrollVertically()5085 public boolean canScrollVertically() { 5086 return false; 5087 } 5088 5089 /** 5090 * Scroll to the specified adapter position. 5091 * 5092 * Actual position of the item on the screen depends on the LayoutManager implementation. 5093 * @param position Scroll to this adapter position. 5094 */ scrollToPosition(int position)5095 public void scrollToPosition(int position) { 5096 if (DEBUG) { 5097 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); 5098 } 5099 } 5100 5101 /** 5102 * <p>Smooth scroll to the specified adapter position.</p> 5103 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} 5104 * instance and call {@link #startSmoothScroll(SmoothScroller)}. 5105 * </p> 5106 * @param recyclerView The RecyclerView to which this layout manager is attached 5107 * @param state Current State of RecyclerView 5108 * @param position Scroll to this adapter position. 5109 */ smoothScrollToPosition(RecyclerView recyclerView, State state, int position)5110 public void smoothScrollToPosition(RecyclerView recyclerView, State state, 5111 int position) { 5112 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); 5113 } 5114 5115 /** 5116 * <p>Starts a smooth scroll using the provided SmoothScroller.</p> 5117 * <p>Calling this method will cancel any previous smooth scroll request.</p> 5118 * @param smoothScroller Unstance which defines how smooth scroll should be animated 5119 */ startSmoothScroll(SmoothScroller smoothScroller)5120 public void startSmoothScroll(SmoothScroller smoothScroller) { 5121 if (mSmoothScroller != null && smoothScroller != mSmoothScroller 5122 && mSmoothScroller.isRunning()) { 5123 mSmoothScroller.stop(); 5124 } 5125 mSmoothScroller = smoothScroller; 5126 mSmoothScroller.start(mRecyclerView, this); 5127 } 5128 5129 /** 5130 * @return true if RecycylerView is currently in the state of smooth scrolling. 5131 */ isSmoothScrolling()5132 public boolean isSmoothScrolling() { 5133 return mSmoothScroller != null && mSmoothScroller.isRunning(); 5134 } 5135 5136 5137 /** 5138 * Returns the resolved layout direction for this RecyclerView. 5139 * 5140 * @return {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout 5141 * direction is RTL or returns 5142 * {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction 5143 * is not RTL. 5144 */ getLayoutDirection()5145 public int getLayoutDirection() { 5146 return ViewCompat.getLayoutDirection(mRecyclerView); 5147 } 5148 5149 /** 5150 * Ends all animations on the view created by the {@link ItemAnimator}. 5151 * 5152 * @param view The View for which the animations should be ended. 5153 * @see RecyclerView.ItemAnimator#endAnimations() 5154 */ endAnimation(View view)5155 public void endAnimation(View view) { 5156 if (mRecyclerView.mItemAnimator != null) { 5157 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); 5158 } 5159 } 5160 5161 /** 5162 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 5163 * to the layout that is known to be going away, either because it has been 5164 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 5165 * visible portion of the container but is being laid out in order to inform RecyclerView 5166 * in how to animate the item out of view. 5167 * <p> 5168 * Views added via this method are going to be invisible to LayoutManager after the 5169 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 5170 * or won't be included in {@link #getChildCount()} method. 5171 * 5172 * @param child View to add and then remove with animation. 5173 */ addDisappearingView(View child)5174 public void addDisappearingView(View child) { 5175 addDisappearingView(child, -1); 5176 } 5177 5178 /** 5179 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 5180 * to the layout that is known to be going away, either because it has been 5181 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 5182 * visible portion of the container but is being laid out in order to inform RecyclerView 5183 * in how to animate the item out of view. 5184 * <p> 5185 * Views added via this method are going to be invisible to LayoutManager after the 5186 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 5187 * or won't be included in {@link #getChildCount()} method. 5188 * 5189 * @param child View to add and then remove with animation. 5190 * @param index Index of the view. 5191 */ addDisappearingView(View child, int index)5192 public void addDisappearingView(View child, int index) { 5193 addViewInt(child, index, true); 5194 } 5195 5196 /** 5197 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 5198 * use this method to add views obtained from a {@link Recycler} using 5199 * {@link Recycler#getViewForPosition(int)}. 5200 * 5201 * @param child View to add 5202 */ addView(View child)5203 public void addView(View child) { 5204 addView(child, -1); 5205 } 5206 5207 /** 5208 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 5209 * use this method to add views obtained from a {@link Recycler} using 5210 * {@link Recycler#getViewForPosition(int)}. 5211 * 5212 * @param child View to add 5213 * @param index Index to add child at 5214 */ addView(View child, int index)5215 public void addView(View child, int index) { 5216 addViewInt(child, index, false); 5217 } 5218 addViewInt(View child, int index, boolean disappearing)5219 private void addViewInt(View child, int index, boolean disappearing) { 5220 final ViewHolder holder = getChildViewHolderInt(child); 5221 if (disappearing || holder.isRemoved()) { 5222 // these views will be hidden at the end of the layout pass. 5223 mRecyclerView.addToDisappearingList(child); 5224 } else { 5225 // This may look like unnecessary but may happen if layout manager supports 5226 // predictive layouts and adapter removed then re-added the same item. 5227 // In this case, added version will be visible in the post layout (because add is 5228 // deferred) but RV will still bind it to the same View. 5229 // So if a View re-appears in post layout pass, remove it from disappearing list. 5230 mRecyclerView.removeFromDisappearingList(child); 5231 } 5232 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 5233 if (holder.wasReturnedFromScrap() || holder.isScrap()) { 5234 if (holder.isScrap()) { 5235 holder.unScrap(); 5236 } else { 5237 holder.clearReturnedFromScrapFlag(); 5238 } 5239 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); 5240 if (DISPATCH_TEMP_DETACH) { 5241 ViewCompat.dispatchFinishTemporaryDetach(child); 5242 } 5243 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child 5244 // ensure in correct position 5245 int currentIndex = mChildHelper.indexOfChild(child); 5246 if (index == -1) { 5247 index = mChildHelper.getChildCount(); 5248 } 5249 if (currentIndex == -1) { 5250 throw new IllegalStateException("Added View has RecyclerView as parent but" 5251 + " view is not a real child. Unfiltered index:" 5252 + mRecyclerView.indexOfChild(child)); 5253 } 5254 if (currentIndex != index) { 5255 mRecyclerView.mLayout.moveView(currentIndex, index); 5256 } 5257 } else { 5258 mChildHelper.addView(child, index, false); 5259 lp.mInsetsDirty = true; 5260 if (mSmoothScroller != null && mSmoothScroller.isRunning()) { 5261 mSmoothScroller.onChildAttachedToWindow(child); 5262 } 5263 } 5264 if (lp.mPendingInvalidate) { 5265 if (DEBUG) { 5266 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); 5267 } 5268 holder.itemView.invalidate(); 5269 lp.mPendingInvalidate = false; 5270 } 5271 } 5272 5273 /** 5274 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 5275 * use this method to completely remove a child view that is no longer needed. 5276 * LayoutManagers should strongly consider recycling removed views using 5277 * {@link Recycler#recycleView(android.view.View)}. 5278 * 5279 * @param child View to remove 5280 */ removeView(View child)5281 public void removeView(View child) { 5282 mChildHelper.removeView(child); 5283 } 5284 5285 /** 5286 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 5287 * use this method to completely remove a child view that is no longer needed. 5288 * LayoutManagers should strongly consider recycling removed views using 5289 * {@link Recycler#recycleView(android.view.View)}. 5290 * 5291 * @param index Index of the child view to remove 5292 */ removeViewAt(int index)5293 public void removeViewAt(int index) { 5294 final View child = getChildAt(index); 5295 if (child != null) { 5296 mChildHelper.removeViewAt(index); 5297 } 5298 } 5299 5300 /** 5301 * Remove all views from the currently attached RecyclerView. This will not recycle 5302 * any of the affected views; the LayoutManager is responsible for doing so if desired. 5303 */ removeAllViews()5304 public void removeAllViews() { 5305 // Only remove non-animating views 5306 final int childCount = getChildCount(); 5307 for (int i = childCount - 1; i >= 0; i--) { 5308 mChildHelper.removeViewAt(i); 5309 } 5310 } 5311 5312 /** 5313 * Returns the adapter position of the item represented by the given View. This does not 5314 * contain any adapter changes that might have happened after the last layout. 5315 * 5316 * @param view The view to query 5317 * @return The adapter position of the item which is rendered by this View. 5318 */ getPosition(View view)5319 public int getPosition(View view) { 5320 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 5321 } 5322 5323 /** 5324 * Returns the View type defined by the adapter. 5325 * 5326 * @param view The view to query 5327 * @return The type of the view assigned by the adapter. 5328 */ getItemViewType(View view)5329 public int getItemViewType(View view) { 5330 return getChildViewHolderInt(view).getItemViewType(); 5331 } 5332 5333 /** 5334 * Finds the view which represents the given adapter position. 5335 * <p> 5336 * This method traverses each child since it has no information about child order. 5337 * Override this method to improve performance if your LayoutManager keeps data about 5338 * child views. 5339 * <p> 5340 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. 5341 * 5342 * @param position Position of the item in adapter 5343 * @return The child view that represents the given position or null if the position is not 5344 * laid out 5345 */ findViewByPosition(int position)5346 public View findViewByPosition(int position) { 5347 final int childCount = getChildCount(); 5348 for (int i = 0; i < childCount; i++) { 5349 View child = getChildAt(i); 5350 ViewHolder vh = getChildViewHolderInt(child); 5351 if (vh == null) { 5352 continue; 5353 } 5354 if (vh.getLayoutPosition() == position && !vh.shouldIgnore() && 5355 (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { 5356 return child; 5357 } 5358 } 5359 return null; 5360 } 5361 5362 /** 5363 * Temporarily detach a child view. 5364 * 5365 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 5366 * views currently attached to the RecyclerView. Generally LayoutManager implementations 5367 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 5368 * so that the detached view may be rebound and reused.</p> 5369 * 5370 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 5371 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 5372 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 5373 * before the LayoutManager entry point method called by RecyclerView returns.</p> 5374 * 5375 * @param child Child to detach 5376 */ detachView(View child)5377 public void detachView(View child) { 5378 final int ind = mChildHelper.indexOfChild(child); 5379 if (ind >= 0) { 5380 detachViewInternal(ind, child); 5381 } 5382 } 5383 5384 /** 5385 * Temporarily detach a child view. 5386 * 5387 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 5388 * views currently attached to the RecyclerView. Generally LayoutManager implementations 5389 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 5390 * so that the detached view may be rebound and reused.</p> 5391 * 5392 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 5393 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 5394 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 5395 * before the LayoutManager entry point method called by RecyclerView returns.</p> 5396 * 5397 * @param index Index of the child to detach 5398 */ detachViewAt(int index)5399 public void detachViewAt(int index) { 5400 detachViewInternal(index, getChildAt(index)); 5401 } 5402 detachViewInternal(int index, View view)5403 private void detachViewInternal(int index, View view) { 5404 if (DISPATCH_TEMP_DETACH) { 5405 ViewCompat.dispatchStartTemporaryDetach(view); 5406 } 5407 mChildHelper.detachViewFromParent(index); 5408 } 5409 5410 /** 5411 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5412 * This method should not be used to reattach views that were previously 5413 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5414 * 5415 * @param child Child to reattach 5416 * @param index Intended child index for child 5417 * @param lp LayoutParams for child 5418 */ attachView(View child, int index, LayoutParams lp)5419 public void attachView(View child, int index, LayoutParams lp) { 5420 ViewHolder vh = getChildViewHolderInt(child); 5421 if (vh.isRemoved()) { 5422 mRecyclerView.addToDisappearingList(child); 5423 } else { 5424 mRecyclerView.removeFromDisappearingList(child); 5425 } 5426 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); 5427 if (DISPATCH_TEMP_DETACH) { 5428 ViewCompat.dispatchFinishTemporaryDetach(child); 5429 } 5430 } 5431 5432 /** 5433 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5434 * This method should not be used to reattach views that were previously 5435 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5436 * 5437 * @param child Child to reattach 5438 * @param index Intended child index for child 5439 */ attachView(View child, int index)5440 public void attachView(View child, int index) { 5441 attachView(child, index, (LayoutParams) child.getLayoutParams()); 5442 } 5443 5444 /** 5445 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5446 * This method should not be used to reattach views that were previously 5447 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5448 * 5449 * @param child Child to reattach 5450 */ attachView(View child)5451 public void attachView(View child) { 5452 attachView(child, -1); 5453 } 5454 5455 /** 5456 * Finish removing a view that was previously temporarily 5457 * {@link #detachView(android.view.View) detached}. 5458 * 5459 * @param child Detached child to remove 5460 */ removeDetachedView(View child)5461 public void removeDetachedView(View child) { 5462 mRecyclerView.removeDetachedView(child, false); 5463 } 5464 5465 /** 5466 * Moves a View from one position to another. 5467 * 5468 * @param fromIndex The View's initial index 5469 * @param toIndex The View's target index 5470 */ moveView(int fromIndex, int toIndex)5471 public void moveView(int fromIndex, int toIndex) { 5472 View view = getChildAt(fromIndex); 5473 if (view == null) { 5474 throw new IllegalArgumentException("Cannot move a child from non-existing index:" 5475 + fromIndex); 5476 } 5477 detachViewAt(fromIndex); 5478 attachView(view, toIndex); 5479 } 5480 5481 /** 5482 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 5483 * 5484 * <p>Scrapping a view allows it to be rebound and reused to show updated or 5485 * different data.</p> 5486 * 5487 * @param child Child to detach and scrap 5488 * @param recycler Recycler to deposit the new scrap view into 5489 */ detachAndScrapView(View child, Recycler recycler)5490 public void detachAndScrapView(View child, Recycler recycler) { 5491 int index = mChildHelper.indexOfChild(child); 5492 scrapOrRecycleView(recycler, index, child); 5493 } 5494 5495 /** 5496 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 5497 * 5498 * <p>Scrapping a view allows it to be rebound and reused to show updated or 5499 * different data.</p> 5500 * 5501 * @param index Index of child to detach and scrap 5502 * @param recycler Recycler to deposit the new scrap view into 5503 */ detachAndScrapViewAt(int index, Recycler recycler)5504 public void detachAndScrapViewAt(int index, Recycler recycler) { 5505 final View child = getChildAt(index); 5506 scrapOrRecycleView(recycler, index, child); 5507 } 5508 5509 /** 5510 * Remove a child view and recycle it using the given Recycler. 5511 * 5512 * @param child Child to remove and recycle 5513 * @param recycler Recycler to use to recycle child 5514 */ removeAndRecycleView(View child, Recycler recycler)5515 public void removeAndRecycleView(View child, Recycler recycler) { 5516 removeView(child); 5517 recycler.recycleView(child); 5518 } 5519 5520 /** 5521 * Remove a child view and recycle it using the given Recycler. 5522 * 5523 * @param index Index of child to remove and recycle 5524 * @param recycler Recycler to use to recycle child 5525 */ removeAndRecycleViewAt(int index, Recycler recycler)5526 public void removeAndRecycleViewAt(int index, Recycler recycler) { 5527 final View view = getChildAt(index); 5528 removeViewAt(index); 5529 recycler.recycleView(view); 5530 } 5531 5532 /** 5533 * Return the current number of child views attached to the parent RecyclerView. 5534 * This does not include child views that were temporarily detached and/or scrapped. 5535 * 5536 * @return Number of attached children 5537 */ getChildCount()5538 public int getChildCount() { 5539 return mChildHelper != null ? mChildHelper.getChildCount() : 0; 5540 } 5541 5542 /** 5543 * Return the child view at the given index 5544 * @param index Index of child to return 5545 * @return Child view at index 5546 */ getChildAt(int index)5547 public View getChildAt(int index) { 5548 return mChildHelper != null ? mChildHelper.getChildAt(index) : null; 5549 } 5550 5551 /** 5552 * Return the width of the parent RecyclerView 5553 * 5554 * @return Width in pixels 5555 */ getWidth()5556 public int getWidth() { 5557 return mRecyclerView != null ? mRecyclerView.getWidth() : 0; 5558 } 5559 5560 /** 5561 * Return the height of the parent RecyclerView 5562 * 5563 * @return Height in pixels 5564 */ getHeight()5565 public int getHeight() { 5566 return mRecyclerView != null ? mRecyclerView.getHeight() : 0; 5567 } 5568 5569 /** 5570 * Return the left padding of the parent RecyclerView 5571 * 5572 * @return Padding in pixels 5573 */ getPaddingLeft()5574 public int getPaddingLeft() { 5575 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 5576 } 5577 5578 /** 5579 * Return the top padding of the parent RecyclerView 5580 * 5581 * @return Padding in pixels 5582 */ getPaddingTop()5583 public int getPaddingTop() { 5584 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 5585 } 5586 5587 /** 5588 * Return the right padding of the parent RecyclerView 5589 * 5590 * @return Padding in pixels 5591 */ getPaddingRight()5592 public int getPaddingRight() { 5593 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 5594 } 5595 5596 /** 5597 * Return the bottom padding of the parent RecyclerView 5598 * 5599 * @return Padding in pixels 5600 */ getPaddingBottom()5601 public int getPaddingBottom() { 5602 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 5603 } 5604 5605 /** 5606 * Return the start padding of the parent RecyclerView 5607 * 5608 * @return Padding in pixels 5609 */ getPaddingStart()5610 public int getPaddingStart() { 5611 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 5612 } 5613 5614 /** 5615 * Return the end padding of the parent RecyclerView 5616 * 5617 * @return Padding in pixels 5618 */ getPaddingEnd()5619 public int getPaddingEnd() { 5620 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 5621 } 5622 5623 /** 5624 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 5625 * 5626 * @return True if the RecyclerView has focus, false otherwise. 5627 * @see View#isFocused() 5628 */ isFocused()5629 public boolean isFocused() { 5630 return mRecyclerView != null && mRecyclerView.isFocused(); 5631 } 5632 5633 /** 5634 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 5635 * 5636 * @return true if the RecyclerView has or contains focus 5637 * @see View#hasFocus() 5638 */ hasFocus()5639 public boolean hasFocus() { 5640 return mRecyclerView != null && mRecyclerView.hasFocus(); 5641 } 5642 5643 /** 5644 * Returns the item View which has or contains focus. 5645 * 5646 * @return A direct child of RecyclerView which has focus or contains the focused child. 5647 */ getFocusedChild()5648 public View getFocusedChild() { 5649 if (mRecyclerView == null) { 5650 return null; 5651 } 5652 final View focused = mRecyclerView.getFocusedChild(); 5653 if (focused == null || mChildHelper.isHidden(focused)) { 5654 return null; 5655 } 5656 return focused; 5657 } 5658 5659 /** 5660 * Returns the number of items in the adapter bound to the parent RecyclerView. 5661 * <p> 5662 * Note that this number is not necessarily equal to {@link State#getItemCount()}. In 5663 * methods where State is available, you should use {@link State#getItemCount()} instead. 5664 * For more details, check the documentation for {@link State#getItemCount()}. 5665 * 5666 * @return The number of items in the bound adapter 5667 * @see State#getItemCount() 5668 */ getItemCount()5669 public int getItemCount() { 5670 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 5671 return a != null ? a.getItemCount() : 0; 5672 } 5673 5674 /** 5675 * Offset all child views attached to the parent RecyclerView by dx pixels along 5676 * the horizontal axis. 5677 * 5678 * @param dx Pixels to offset by 5679 */ offsetChildrenHorizontal(int dx)5680 public void offsetChildrenHorizontal(int dx) { 5681 if (mRecyclerView != null) { 5682 mRecyclerView.offsetChildrenHorizontal(dx); 5683 } 5684 } 5685 5686 /** 5687 * Offset all child views attached to the parent RecyclerView by dy pixels along 5688 * the vertical axis. 5689 * 5690 * @param dy Pixels to offset by 5691 */ offsetChildrenVertical(int dy)5692 public void offsetChildrenVertical(int dy) { 5693 if (mRecyclerView != null) { 5694 mRecyclerView.offsetChildrenVertical(dy); 5695 } 5696 } 5697 5698 /** 5699 * Flags a view so that it will not be scrapped or recycled. 5700 * <p> 5701 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and 5702 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child 5703 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not 5704 * ignore the child. 5705 * <p> 5706 * Before this child can be recycled again, you have to call 5707 * {@link #stopIgnoringView(View)}. 5708 * <p> 5709 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 5710 * 5711 * @param view View to ignore. 5712 * @see #stopIgnoringView(View) 5713 */ ignoreView(View view)5714 public void ignoreView(View view) { 5715 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { 5716 // checking this because calling this method on a recycled or detached view may 5717 // cause loss of state. 5718 throw new IllegalArgumentException("View should be fully attached to be ignored"); 5719 } 5720 final ViewHolder vh = getChildViewHolderInt(view); 5721 vh.addFlags(ViewHolder.FLAG_IGNORE); 5722 mRecyclerView.mState.onViewIgnored(vh); 5723 } 5724 5725 /** 5726 * View can be scrapped and recycled again. 5727 * <p> 5728 * Note that calling this method removes all information in the view holder. 5729 * <p> 5730 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 5731 * 5732 * @param view View to ignore. 5733 */ stopIgnoringView(View view)5734 public void stopIgnoringView(View view) { 5735 final ViewHolder vh = getChildViewHolderInt(view); 5736 vh.stopIgnoring(); 5737 vh.resetInternal(); 5738 vh.addFlags(ViewHolder.FLAG_INVALID); 5739 } 5740 5741 /** 5742 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 5743 * into the given Recycler. The Recycler may prefer to reuse scrap views before 5744 * other views that were previously recycled. 5745 * 5746 * @param recycler Recycler to scrap views into 5747 */ detachAndScrapAttachedViews(Recycler recycler)5748 public void detachAndScrapAttachedViews(Recycler recycler) { 5749 final int childCount = getChildCount(); 5750 for (int i = childCount - 1; i >= 0; i--) { 5751 final View v = getChildAt(i); 5752 scrapOrRecycleView(recycler, i, v); 5753 } 5754 } 5755 scrapOrRecycleView(Recycler recycler, int index, View view)5756 private void scrapOrRecycleView(Recycler recycler, int index, View view) { 5757 final ViewHolder viewHolder = getChildViewHolderInt(view); 5758 if (viewHolder.shouldIgnore()) { 5759 if (DEBUG) { 5760 Log.d(TAG, "ignoring view " + viewHolder); 5761 } 5762 return; 5763 } 5764 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() && 5765 !mRecyclerView.mAdapter.hasStableIds()) { 5766 removeViewAt(index); 5767 recycler.recycleViewHolderInternal(viewHolder); 5768 } else { 5769 detachViewAt(index); 5770 recycler.scrapView(view); 5771 } 5772 } 5773 5774 /** 5775 * Recycles the scrapped views. 5776 * <p> 5777 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is 5778 * the expected behavior if scrapped views are used for animations. Otherwise, we need to 5779 * call remove and invalidate RecyclerView to ensure UI update. 5780 * 5781 * @param recycler Recycler 5782 */ removeAndRecycleScrapInt(Recycler recycler)5783 void removeAndRecycleScrapInt(Recycler recycler) { 5784 final int scrapCount = recycler.getScrapCount(); 5785 for (int i = 0; i < scrapCount; i++) { 5786 final View scrap = recycler.getScrapViewAt(i); 5787 final ViewHolder vh = getChildViewHolderInt(scrap); 5788 if (vh.shouldIgnore()) { 5789 continue; 5790 } 5791 if (vh.isTmpDetached()) { 5792 mRecyclerView.removeDetachedView(scrap, false); 5793 } 5794 recycler.quickRecycleScrapView(scrap); 5795 } 5796 recycler.clearScrap(); 5797 if (scrapCount > 0) { 5798 mRecyclerView.invalidate(); 5799 } 5800 } 5801 5802 5803 /** 5804 * Measure a child view using standard measurement policy, taking the padding 5805 * of the parent RecyclerView and any added item decorations into account. 5806 * 5807 * <p>If the RecyclerView can be scrolled in either dimension the caller may 5808 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 5809 * 5810 * @param child Child view to measure 5811 * @param widthUsed Width in pixels currently consumed by other views, if relevant 5812 * @param heightUsed Height in pixels currently consumed by other views, if relevant 5813 */ measureChild(View child, int widthUsed, int heightUsed)5814 public void measureChild(View child, int widthUsed, int heightUsed) { 5815 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 5816 5817 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 5818 widthUsed += insets.left + insets.right; 5819 heightUsed += insets.top + insets.bottom; 5820 5821 final int widthSpec = getChildMeasureSpec(getWidth(), 5822 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 5823 canScrollHorizontally()); 5824 final int heightSpec = getChildMeasureSpec(getHeight(), 5825 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 5826 canScrollVertically()); 5827 child.measure(widthSpec, heightSpec); 5828 } 5829 5830 /** 5831 * Measure a child view using standard measurement policy, taking the padding 5832 * of the parent RecyclerView, any added item decorations and the child margins 5833 * into account. 5834 * 5835 * <p>If the RecyclerView can be scrolled in either dimension the caller may 5836 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 5837 * 5838 * @param child Child view to measure 5839 * @param widthUsed Width in pixels currently consumed by other views, if relevant 5840 * @param heightUsed Height in pixels currently consumed by other views, if relevant 5841 */ measureChildWithMargins(View child, int widthUsed, int heightUsed)5842 public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { 5843 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 5844 5845 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 5846 widthUsed += insets.left + insets.right; 5847 heightUsed += insets.top + insets.bottom; 5848 5849 final int widthSpec = getChildMeasureSpec(getWidth(), 5850 getPaddingLeft() + getPaddingRight() + 5851 lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 5852 canScrollHorizontally()); 5853 final int heightSpec = getChildMeasureSpec(getHeight(), 5854 getPaddingTop() + getPaddingBottom() + 5855 lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 5856 canScrollVertically()); 5857 child.measure(widthSpec, heightSpec); 5858 } 5859 5860 /** 5861 * Calculate a MeasureSpec value for measuring a child view in one dimension. 5862 * 5863 * @param parentSize Size of the parent view where the child will be placed 5864 * @param padding Total space currently consumed by other elements of parent 5865 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 5866 * Generally obtained from the child view's LayoutParams 5867 * @param canScroll true if the parent RecyclerView can scroll in this dimension 5868 * 5869 * @return a MeasureSpec value for the child view 5870 */ getChildMeasureSpec(int parentSize, int padding, int childDimension, boolean canScroll)5871 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 5872 boolean canScroll) { 5873 int size = Math.max(0, parentSize - padding); 5874 int resultSize = 0; 5875 int resultMode = 0; 5876 5877 if (canScroll) { 5878 if (childDimension >= 0) { 5879 resultSize = childDimension; 5880 resultMode = MeasureSpec.EXACTLY; 5881 } else { 5882 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 5883 // instead using UNSPECIFIED. 5884 resultSize = 0; 5885 resultMode = MeasureSpec.UNSPECIFIED; 5886 } 5887 } else { 5888 if (childDimension >= 0) { 5889 resultSize = childDimension; 5890 resultMode = MeasureSpec.EXACTLY; 5891 } else if (childDimension == LayoutParams.FILL_PARENT) { 5892 resultSize = size; 5893 resultMode = MeasureSpec.EXACTLY; 5894 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 5895 resultSize = size; 5896 resultMode = MeasureSpec.AT_MOST; 5897 } 5898 } 5899 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 5900 } 5901 5902 /** 5903 * Returns the measured width of the given child, plus the additional size of 5904 * any insets applied by {@link ItemDecoration ItemDecorations}. 5905 * 5906 * @param child Child view to query 5907 * @return child's measured width plus <code>ItemDecoration</code> insets 5908 * 5909 * @see View#getMeasuredWidth() 5910 */ getDecoratedMeasuredWidth(View child)5911 public int getDecoratedMeasuredWidth(View child) { 5912 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 5913 return child.getMeasuredWidth() + insets.left + insets.right; 5914 } 5915 5916 /** 5917 * Returns the measured height of the given child, plus the additional size of 5918 * any insets applied by {@link ItemDecoration ItemDecorations}. 5919 * 5920 * @param child Child view to query 5921 * @return child's measured height plus <code>ItemDecoration</code> insets 5922 * 5923 * @see View#getMeasuredHeight() 5924 */ getDecoratedMeasuredHeight(View child)5925 public int getDecoratedMeasuredHeight(View child) { 5926 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 5927 return child.getMeasuredHeight() + insets.top + insets.bottom; 5928 } 5929 5930 /** 5931 * Lay out the given child view within the RecyclerView using coordinates that 5932 * include any current {@link ItemDecoration ItemDecorations}. 5933 * 5934 * <p>LayoutManagers should prefer working in sizes and coordinates that include 5935 * item decoration insets whenever possible. This allows the LayoutManager to effectively 5936 * ignore decoration insets within measurement and layout code. See the following 5937 * methods:</p> 5938 * <ul> 5939 * <li>{@link #measureChild(View, int, int)}</li> 5940 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 5941 * <li>{@link #getDecoratedLeft(View)}</li> 5942 * <li>{@link #getDecoratedTop(View)}</li> 5943 * <li>{@link #getDecoratedRight(View)}</li> 5944 * <li>{@link #getDecoratedBottom(View)}</li> 5945 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 5946 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 5947 * </ul> 5948 * 5949 * @param child Child to lay out 5950 * @param left Left edge, with item decoration insets included 5951 * @param top Top edge, with item decoration insets included 5952 * @param right Right edge, with item decoration insets included 5953 * @param bottom Bottom edge, with item decoration insets included 5954 * 5955 * @see View#layout(int, int, int, int) 5956 */ layoutDecorated(View child, int left, int top, int right, int bottom)5957 public void layoutDecorated(View child, int left, int top, int right, int bottom) { 5958 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 5959 child.layout(left + insets.left, top + insets.top, right - insets.right, 5960 bottom - insets.bottom); 5961 } 5962 5963 /** 5964 * Returns the left edge of the given child view within its parent, offset by any applied 5965 * {@link ItemDecoration ItemDecorations}. 5966 * 5967 * @param child Child to query 5968 * @return Child left edge with offsets applied 5969 * @see #getLeftDecorationWidth(View) 5970 */ getDecoratedLeft(View child)5971 public int getDecoratedLeft(View child) { 5972 return child.getLeft() - getLeftDecorationWidth(child); 5973 } 5974 5975 /** 5976 * Returns the top edge of the given child view within its parent, offset by any applied 5977 * {@link ItemDecoration ItemDecorations}. 5978 * 5979 * @param child Child to query 5980 * @return Child top edge with offsets applied 5981 * @see #getTopDecorationHeight(View) 5982 */ getDecoratedTop(View child)5983 public int getDecoratedTop(View child) { 5984 return child.getTop() - getTopDecorationHeight(child); 5985 } 5986 5987 /** 5988 * Returns the right edge of the given child view within its parent, offset by any applied 5989 * {@link ItemDecoration ItemDecorations}. 5990 * 5991 * @param child Child to query 5992 * @return Child right edge with offsets applied 5993 * @see #getRightDecorationWidth(View) 5994 */ getDecoratedRight(View child)5995 public int getDecoratedRight(View child) { 5996 return child.getRight() + getRightDecorationWidth(child); 5997 } 5998 5999 /** 6000 * Returns the bottom edge of the given child view within its parent, offset by any applied 6001 * {@link ItemDecoration ItemDecorations}. 6002 * 6003 * @param child Child to query 6004 * @return Child bottom edge with offsets applied 6005 * @see #getBottomDecorationHeight(View) 6006 */ getDecoratedBottom(View child)6007 public int getDecoratedBottom(View child) { 6008 return child.getBottom() + getBottomDecorationHeight(child); 6009 } 6010 6011 /** 6012 * Calculates the item decor insets applied to the given child and updates the provided 6013 * Rect instance with the inset values. 6014 * <ul> 6015 * <li>The Rect's left is set to the total width of left decorations.</li> 6016 * <li>The Rect's top is set to the total height of top decorations.</li> 6017 * <li>The Rect's right is set to the total width of right decorations.</li> 6018 * <li>The Rect's bottom is set to total height of bottom decorations.</li> 6019 * </ul> 6020 * <p> 6021 * Note that item decorations are automatically calculated when one of the LayoutManager's 6022 * measure child methods is called. If you need to measure the child with custom specs via 6023 * {@link View#measure(int, int)}, you can use this method to get decorations. 6024 * 6025 * @param child The child view whose decorations should be calculated 6026 * @param outRect The Rect to hold result values 6027 */ calculateItemDecorationsForChild(View child, Rect outRect)6028 public void calculateItemDecorationsForChild(View child, Rect outRect) { 6029 if (mRecyclerView == null) { 6030 outRect.set(0, 0, 0, 0); 6031 return; 6032 } 6033 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 6034 outRect.set(insets); 6035 } 6036 6037 /** 6038 * Returns the total height of item decorations applied to child's top. 6039 * <p> 6040 * Note that this value is not updated until the View is measured or 6041 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6042 * 6043 * @param child Child to query 6044 * @return The total height of item decorations applied to the child's top. 6045 * @see #getDecoratedTop(View) 6046 * @see #calculateItemDecorationsForChild(View, Rect) 6047 */ getTopDecorationHeight(View child)6048 public int getTopDecorationHeight(View child) { 6049 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; 6050 } 6051 6052 /** 6053 * Returns the total height of item decorations applied to child's bottom. 6054 * <p> 6055 * Note that this value is not updated until the View is measured or 6056 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6057 * 6058 * @param child Child to query 6059 * @return The total height of item decorations applied to the child's bottom. 6060 * @see #getDecoratedBottom(View) 6061 * @see #calculateItemDecorationsForChild(View, Rect) 6062 */ getBottomDecorationHeight(View child)6063 public int getBottomDecorationHeight(View child) { 6064 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; 6065 } 6066 6067 /** 6068 * Returns the total width of item decorations applied to child's left. 6069 * <p> 6070 * Note that this value is not updated until the View is measured or 6071 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6072 * 6073 * @param child Child to query 6074 * @return The total width of item decorations applied to the child's left. 6075 * @see #getDecoratedLeft(View) 6076 * @see #calculateItemDecorationsForChild(View, Rect) 6077 */ getLeftDecorationWidth(View child)6078 public int getLeftDecorationWidth(View child) { 6079 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; 6080 } 6081 6082 /** 6083 * Returns the total width of item decorations applied to child's right. 6084 * <p> 6085 * Note that this value is not updated until the View is measured or 6086 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6087 * 6088 * @param child Child to query 6089 * @return The total width of item decorations applied to the child's right. 6090 * @see #getDecoratedRight(View) 6091 * @see #calculateItemDecorationsForChild(View, Rect) 6092 */ getRightDecorationWidth(View child)6093 public int getRightDecorationWidth(View child) { 6094 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; 6095 } 6096 6097 /** 6098 * Called when searching for a focusable view in the given direction has failed 6099 * for the current content of the RecyclerView. 6100 * 6101 * <p>This is the LayoutManager's opportunity to populate views in the given direction 6102 * to fulfill the request if it can. The LayoutManager should attach and return 6103 * the view to be focused. The default implementation returns null.</p> 6104 * 6105 * @param focused The currently focused view 6106 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6107 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6108 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6109 * or 0 for not applicable 6110 * @param recycler The recycler to use for obtaining views for currently offscreen items 6111 * @param state Transient state of RecyclerView 6112 * @return The chosen view to be focused 6113 */ onFocusSearchFailed(View focused, int direction, Recycler recycler, State state)6114 public View onFocusSearchFailed(View focused, int direction, Recycler recycler, 6115 State state) { 6116 return null; 6117 } 6118 6119 /** 6120 * This method gives a LayoutManager an opportunity to intercept the initial focus search 6121 * before the default behavior of {@link FocusFinder} is used. If this method returns 6122 * null FocusFinder will attempt to find a focusable child view. If it fails 6123 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} 6124 * will be called to give the LayoutManager an opportunity to add new views for items 6125 * that did not have attached views representing them. The LayoutManager should not add 6126 * or remove views from this method. 6127 * 6128 * @param focused The currently focused view 6129 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6130 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6131 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6132 * @return A descendant view to focus or null to fall back to default behavior. 6133 * The default implementation returns null. 6134 */ onInterceptFocusSearch(View focused, int direction)6135 public View onInterceptFocusSearch(View focused, int direction) { 6136 return null; 6137 } 6138 6139 /** 6140 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 6141 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 6142 * android.graphics.Rect, boolean)} for more details. 6143 * 6144 * <p>The base implementation will attempt to perform a standard programmatic scroll 6145 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 6146 * 6147 * @param child The direct child making the request. 6148 * @param rect The rectangle in the child's coordinates the child 6149 * wishes to be on the screen. 6150 * @param immediate True to forbid animated or delayed scrolling, 6151 * false otherwise 6152 * @return Whether the group scrolled to handle the operation 6153 */ requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, boolean immediate)6154 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, 6155 boolean immediate) { 6156 final int parentLeft = getPaddingLeft(); 6157 final int parentTop = getPaddingTop(); 6158 final int parentRight = getWidth() - getPaddingRight(); 6159 final int parentBottom = getHeight() - getPaddingBottom(); 6160 final int childLeft = child.getLeft() + rect.left; 6161 final int childTop = child.getTop() + rect.top; 6162 final int childRight = childLeft + rect.width(); 6163 final int childBottom = childTop + rect.height(); 6164 6165 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 6166 final int offScreenTop = Math.min(0, childTop - parentTop); 6167 final int offScreenRight = Math.max(0, childRight - parentRight); 6168 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 6169 6170 // Favor the "start" layout direction over the end when bringing one side or the other 6171 // of a large rect into view. 6172 final int dx; 6173 if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) { 6174 dx = offScreenRight != 0 ? offScreenRight : offScreenLeft; 6175 } else { 6176 dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight; 6177 } 6178 6179 // Favor bringing the top into view over the bottom 6180 final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom; 6181 6182 if (dx != 0 || dy != 0) { 6183 if (immediate) { 6184 parent.scrollBy(dx, dy); 6185 } else { 6186 parent.smoothScrollBy(dx, dy); 6187 } 6188 return true; 6189 } 6190 return false; 6191 } 6192 6193 /** 6194 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} 6195 */ 6196 @Deprecated onRequestChildFocus(RecyclerView parent, View child, View focused)6197 public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { 6198 // eat the request if we are in the middle of a scroll or layout 6199 return isSmoothScrolling() || parent.mRunningLayoutOrScroll; 6200 } 6201 6202 /** 6203 * Called when a descendant view of the RecyclerView requests focus. 6204 * 6205 * <p>A LayoutManager wishing to keep focused views aligned in a specific 6206 * portion of the view may implement that behavior in an override of this method.</p> 6207 * 6208 * <p>If the LayoutManager executes different behavior that should override the default 6209 * behavior of scrolling the focused child on screen instead of running alongside it, 6210 * this method should return true.</p> 6211 * 6212 * @param parent The RecyclerView hosting this LayoutManager 6213 * @param state Current state of RecyclerView 6214 * @param child Direct child of the RecyclerView containing the newly focused view 6215 * @param focused The newly focused view. This may be the same view as child or it may be 6216 * null 6217 * @return true if the default scroll behavior should be suppressed 6218 */ onRequestChildFocus(RecyclerView parent, State state, View child, View focused)6219 public boolean onRequestChildFocus(RecyclerView parent, State state, View child, 6220 View focused) { 6221 return onRequestChildFocus(parent, child, focused); 6222 } 6223 6224 /** 6225 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. 6226 * The LayoutManager may use this opportunity to clear caches and configure state such 6227 * that it can relayout appropriately with the new data and potentially new view types. 6228 * 6229 * <p>The default implementation removes all currently attached views.</p> 6230 * 6231 * @param oldAdapter The previous adapter instance. Will be null if there was previously no 6232 * adapter. 6233 * @param newAdapter The new adapter instance. Might be null if 6234 * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}. 6235 */ onAdapterChanged(Adapter oldAdapter, Adapter newAdapter)6236 public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { 6237 } 6238 6239 /** 6240 * Called to populate focusable views within the RecyclerView. 6241 * 6242 * <p>The LayoutManager implementation should return <code>true</code> if the default 6243 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 6244 * suppressed.</p> 6245 * 6246 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 6247 * to fall back to the default ViewGroup behavior.</p> 6248 * 6249 * @param recyclerView The RecyclerView hosting this LayoutManager 6250 * @param views List of output views. This method should add valid focusable views 6251 * to this list. 6252 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6253 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6254 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6255 * @param focusableMode The type of focusables to be added. 6256 * 6257 * @return true to suppress the default behavior, false to add default focusables after 6258 * this method returns. 6259 * 6260 * @see #FOCUSABLES_ALL 6261 * @see #FOCUSABLES_TOUCH_MODE 6262 */ onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode)6263 public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, 6264 int direction, int focusableMode) { 6265 return false; 6266 } 6267 6268 /** 6269 * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving 6270 * detailed information on what has actually changed. 6271 * 6272 * @param recyclerView 6273 */ onItemsChanged(RecyclerView recyclerView)6274 public void onItemsChanged(RecyclerView recyclerView) { 6275 } 6276 6277 /** 6278 * Called when items have been added to the adapter. The LayoutManager may choose to 6279 * requestLayout if the inserted items would require refreshing the currently visible set 6280 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 6281 * 6282 * @param recyclerView 6283 * @param positionStart 6284 * @param itemCount 6285 */ onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)6286 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 6287 } 6288 6289 /** 6290 * Called when items have been removed from the adapter. 6291 * 6292 * @param recyclerView 6293 * @param positionStart 6294 * @param itemCount 6295 */ onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)6296 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { 6297 } 6298 6299 /** 6300 * Called when items have been changed in the adapter. 6301 * 6302 * @param recyclerView 6303 * @param positionStart 6304 * @param itemCount 6305 */ onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount)6306 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { 6307 } 6308 6309 /** 6310 * Called when an item is moved withing the adapter. 6311 * <p> 6312 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE 6313 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} 6314 * is called. 6315 * 6316 * @param recyclerView 6317 * @param from 6318 * @param to 6319 * @param itemCount 6320 */ onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount)6321 public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { 6322 6323 } 6324 6325 6326 /** 6327 * <p>Override this method if you want to support scroll bars.</p> 6328 * 6329 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> 6330 * 6331 * <p>Default implementation returns 0.</p> 6332 * 6333 * @param state Current state of RecyclerView 6334 * @return The horizontal extent of the scrollbar's thumb 6335 * @see RecyclerView#computeHorizontalScrollExtent() 6336 */ computeHorizontalScrollExtent(State state)6337 public int computeHorizontalScrollExtent(State state) { 6338 return 0; 6339 } 6340 6341 /** 6342 * <p>Override this method if you want to support scroll bars.</p> 6343 * 6344 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> 6345 * 6346 * <p>Default implementation returns 0.</p> 6347 * 6348 * @param state Current State of RecyclerView where you can find total item count 6349 * @return The horizontal offset of the scrollbar's thumb 6350 * @see RecyclerView#computeHorizontalScrollOffset() 6351 */ computeHorizontalScrollOffset(State state)6352 public int computeHorizontalScrollOffset(State state) { 6353 return 0; 6354 } 6355 6356 /** 6357 * <p>Override this method if you want to support scroll bars.</p> 6358 * 6359 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> 6360 * 6361 * <p>Default implementation returns 0.</p> 6362 * 6363 * @param state Current State of RecyclerView where you can find total item count 6364 * @return The total horizontal range represented by the vertical scrollbar 6365 * @see RecyclerView#computeHorizontalScrollRange() 6366 */ computeHorizontalScrollRange(State state)6367 public int computeHorizontalScrollRange(State state) { 6368 return 0; 6369 } 6370 6371 /** 6372 * <p>Override this method if you want to support scroll bars.</p> 6373 * 6374 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> 6375 * 6376 * <p>Default implementation returns 0.</p> 6377 * 6378 * @param state Current state of RecyclerView 6379 * @return The vertical extent of the scrollbar's thumb 6380 * @see RecyclerView#computeVerticalScrollExtent() 6381 */ computeVerticalScrollExtent(State state)6382 public int computeVerticalScrollExtent(State state) { 6383 return 0; 6384 } 6385 6386 /** 6387 * <p>Override this method if you want to support scroll bars.</p> 6388 * 6389 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> 6390 * 6391 * <p>Default implementation returns 0.</p> 6392 * 6393 * @param state Current State of RecyclerView where you can find total item count 6394 * @return The vertical offset of the scrollbar's thumb 6395 * @see RecyclerView#computeVerticalScrollOffset() 6396 */ computeVerticalScrollOffset(State state)6397 public int computeVerticalScrollOffset(State state) { 6398 return 0; 6399 } 6400 6401 /** 6402 * <p>Override this method if you want to support scroll bars.</p> 6403 * 6404 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> 6405 * 6406 * <p>Default implementation returns 0.</p> 6407 * 6408 * @param state Current State of RecyclerView where you can find total item count 6409 * @return The total vertical range represented by the vertical scrollbar 6410 * @see RecyclerView#computeVerticalScrollRange() 6411 */ computeVerticalScrollRange(State state)6412 public int computeVerticalScrollRange(State state) { 6413 return 0; 6414 } 6415 6416 /** 6417 * Measure the attached RecyclerView. Implementations must call 6418 * {@link #setMeasuredDimension(int, int)} before returning. 6419 * 6420 * <p>The default implementation will handle EXACTLY measurements and respect 6421 * the minimum width and height properties of the host RecyclerView if measured 6422 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView 6423 * will consume all available space.</p> 6424 * 6425 * @param recycler Recycler 6426 * @param state Transient state of RecyclerView 6427 * @param widthSpec Width {@link android.view.View.MeasureSpec} 6428 * @param heightSpec Height {@link android.view.View.MeasureSpec} 6429 */ onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec)6430 public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { 6431 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 6432 } 6433 6434 /** 6435 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the 6436 * host RecyclerView. 6437 * 6438 * @param widthSize Measured width 6439 * @param heightSize Measured height 6440 */ setMeasuredDimension(int widthSize, int heightSize)6441 public void setMeasuredDimension(int widthSize, int heightSize) { 6442 mRecyclerView.setMeasuredDimension(widthSize, heightSize); 6443 } 6444 6445 /** 6446 * @return The host RecyclerView's {@link View#getMinimumWidth()} 6447 */ getMinimumWidth()6448 public int getMinimumWidth() { 6449 return ViewCompat.getMinimumWidth(mRecyclerView); 6450 } 6451 6452 /** 6453 * @return The host RecyclerView's {@link View#getMinimumHeight()} 6454 */ getMinimumHeight()6455 public int getMinimumHeight() { 6456 return ViewCompat.getMinimumHeight(mRecyclerView); 6457 } 6458 /** 6459 * <p>Called when the LayoutManager should save its state. This is a good time to save your 6460 * scroll position, configuration and anything else that may be required to restore the same 6461 * layout state if the LayoutManager is recreated.</p> 6462 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and 6463 * restore. This will let you share information between your LayoutManagers but it is also 6464 * your responsibility to make sure they use the same parcelable class.</p> 6465 * 6466 * @return Necessary information for LayoutManager to be able to restore its state 6467 */ onSaveInstanceState()6468 public Parcelable onSaveInstanceState() { 6469 return null; 6470 } 6471 6472 onRestoreInstanceState(Parcelable state)6473 public void onRestoreInstanceState(Parcelable state) { 6474 6475 } 6476 stopSmoothScroller()6477 void stopSmoothScroller() { 6478 if (mSmoothScroller != null) { 6479 mSmoothScroller.stop(); 6480 } 6481 } 6482 onSmoothScrollerStopped(SmoothScroller smoothScroller)6483 private void onSmoothScrollerStopped(SmoothScroller smoothScroller) { 6484 if (mSmoothScroller == smoothScroller) { 6485 mSmoothScroller = null; 6486 } 6487 } 6488 6489 /** 6490 * RecyclerView calls this method to notify LayoutManager that scroll state has changed. 6491 * 6492 * @param state The new scroll state for RecyclerView 6493 */ onScrollStateChanged(int state)6494 public void onScrollStateChanged(int state) { 6495 } 6496 6497 /** 6498 * Removes all views and recycles them using the given recycler. 6499 * <p> 6500 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. 6501 * <p> 6502 * If a View is marked as "ignored", it is not removed nor recycled. 6503 * 6504 * @param recycler Recycler to use to recycle children 6505 * @see #removeAndRecycleView(View, Recycler) 6506 * @see #removeAndRecycleViewAt(int, Recycler) 6507 * @see #ignoreView(View) 6508 */ removeAndRecycleAllViews(Recycler recycler)6509 public void removeAndRecycleAllViews(Recycler recycler) { 6510 for (int i = getChildCount() - 1; i >= 0; i--) { 6511 final View view = getChildAt(i); 6512 if (!getChildViewHolderInt(view).shouldIgnore()) { 6513 removeAndRecycleViewAt(i, recycler); 6514 } 6515 } 6516 } 6517 6518 // called by accessibility delegate onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info)6519 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 6520 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, 6521 info); 6522 } 6523 6524 /** 6525 * Called by the AccessibilityDelegate when the information about the current layout should 6526 * be populated. 6527 * <p> 6528 * Default implementation adds a {@link 6529 * android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. 6530 * <p> 6531 * You should override 6532 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 6533 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 6534 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and 6535 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for 6536 * more accurate accessibility information. 6537 * 6538 * @param recycler The Recycler that can be used to convert view positions into adapter 6539 * positions 6540 * @param state The current state of RecyclerView 6541 * @param info The info that should be filled by the LayoutManager 6542 * @see View#onInitializeAccessibilityNodeInfo( 6543 *android.view.accessibility.AccessibilityNodeInfo) 6544 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6545 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6546 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) 6547 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6548 */ onInitializeAccessibilityNodeInfo(Recycler recycler, State state, AccessibilityNodeInfoCompat info)6549 public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state, 6550 AccessibilityNodeInfoCompat info) { 6551 info.setClassName(RecyclerView.class.getName()); 6552 if (ViewCompat.canScrollVertically(mRecyclerView, -1) || 6553 ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { 6554 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 6555 info.setScrollable(true); 6556 } 6557 if (ViewCompat.canScrollVertically(mRecyclerView, 1) || 6558 ViewCompat.canScrollHorizontally(mRecyclerView, 1)) { 6559 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 6560 info.setScrollable(true); 6561 } 6562 final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo 6563 = AccessibilityNodeInfoCompat.CollectionInfoCompat 6564 .obtain(getRowCountForAccessibility(recycler, state), 6565 getColumnCountForAccessibility(recycler, state), 6566 isLayoutHierarchical(recycler, state), 6567 getSelectionModeForAccessibility(recycler, state)); 6568 info.setCollectionInfo(collectionInfo); 6569 } 6570 6571 // called by accessibility delegate onInitializeAccessibilityEvent(AccessibilityEvent event)6572 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 6573 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); 6574 } 6575 6576 /** 6577 * Called by the accessibility delegate to initialize an accessibility event. 6578 * <p> 6579 * Default implementation adds item count and scroll information to the event. 6580 * 6581 * @param recycler The Recycler that can be used to convert view positions into adapter 6582 * positions 6583 * @param state The current state of RecyclerView 6584 * @param event The event instance to initialize 6585 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) 6586 */ onInitializeAccessibilityEvent(Recycler recycler, State state, AccessibilityEvent event)6587 public void onInitializeAccessibilityEvent(Recycler recycler, State state, 6588 AccessibilityEvent event) { 6589 final AccessibilityRecordCompat record = AccessibilityEventCompat 6590 .asRecord(event); 6591 if (mRecyclerView == null || record == null) { 6592 return; 6593 } 6594 record.setScrollable(ViewCompat.canScrollVertically(mRecyclerView, 1) 6595 || ViewCompat.canScrollVertically(mRecyclerView, -1) 6596 || ViewCompat.canScrollHorizontally(mRecyclerView, -1) 6597 || ViewCompat.canScrollHorizontally(mRecyclerView, 1)); 6598 6599 if (mRecyclerView.mAdapter != null) { 6600 record.setItemCount(mRecyclerView.mAdapter.getItemCount()); 6601 } 6602 } 6603 6604 // called by accessibility delegate onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info)6605 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { 6606 final ViewHolder vh = getChildViewHolderInt(host); 6607 // avoid trying to create accessibility node info for removed children 6608 if (vh != null && !vh.isRemoved()) { 6609 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, 6610 mRecyclerView.mState, host, info); 6611 } 6612 } 6613 6614 /** 6615 * Called by the AccessibilityDelegate when the accessibility information for a specific 6616 * item should be populated. 6617 * <p> 6618 * Default implementation adds basic positioning information about the item. 6619 * 6620 * @param recycler The Recycler that can be used to convert view positions into adapter 6621 * positions 6622 * @param state The current state of RecyclerView 6623 * @param host The child for which accessibility node info should be populated 6624 * @param info The info to fill out about the item 6625 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, 6626 * android.view.accessibility.AccessibilityNodeInfo) 6627 */ onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, View host, AccessibilityNodeInfoCompat info)6628 public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, 6629 View host, AccessibilityNodeInfoCompat info) { 6630 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; 6631 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; 6632 final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo 6633 = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, 6634 columnIndexGuess, 1, false, false); 6635 info.setCollectionItemInfo(itemInfo); 6636 } 6637 6638 /** 6639 * A LayoutManager can call this method to force RecyclerView to run simple animations in 6640 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data 6641 * change). 6642 * <p> 6643 * Note that, calling this method will not guarantee that RecyclerView will run animations 6644 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will 6645 * not run any animations but will still clear this flag after the layout is complete. 6646 * 6647 */ requestSimpleAnimationsInNextLayout()6648 public void requestSimpleAnimationsInNextLayout() { 6649 mRequestedSimpleAnimations = true; 6650 } 6651 6652 /** 6653 * Returns the selection mode for accessibility. Should be 6654 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, 6655 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or 6656 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. 6657 * <p> 6658 * Default implementation returns 6659 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 6660 * 6661 * @param recycler The Recycler that can be used to convert view positions into adapter 6662 * positions 6663 * @param state The current state of RecyclerView 6664 * @return Selection mode for accessibility. Default implementation returns 6665 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 6666 */ getSelectionModeForAccessibility(Recycler recycler, State state)6667 public int getSelectionModeForAccessibility(Recycler recycler, State state) { 6668 return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; 6669 } 6670 6671 /** 6672 * Returns the number of rows for accessibility. 6673 * <p> 6674 * Default implementation returns the number of items in the adapter if LayoutManager 6675 * supports vertical scrolling or 1 if LayoutManager does not support vertical 6676 * scrolling. 6677 * 6678 * @param recycler The Recycler that can be used to convert view positions into adapter 6679 * positions 6680 * @param state The current state of RecyclerView 6681 * @return The number of rows in LayoutManager for accessibility. 6682 */ getRowCountForAccessibility(Recycler recycler, State state)6683 public int getRowCountForAccessibility(Recycler recycler, State state) { 6684 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 6685 return 1; 6686 } 6687 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; 6688 } 6689 6690 /** 6691 * Returns the number of columns for accessibility. 6692 * <p> 6693 * Default implementation returns the number of items in the adapter if LayoutManager 6694 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal 6695 * scrolling. 6696 * 6697 * @param recycler The Recycler that can be used to convert view positions into adapter 6698 * positions 6699 * @param state The current state of RecyclerView 6700 * @return The number of rows in LayoutManager for accessibility. 6701 */ getColumnCountForAccessibility(Recycler recycler, State state)6702 public int getColumnCountForAccessibility(Recycler recycler, State state) { 6703 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 6704 return 1; 6705 } 6706 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; 6707 } 6708 6709 /** 6710 * Returns whether layout is hierarchical or not to be used for accessibility. 6711 * <p> 6712 * Default implementation returns false. 6713 * 6714 * @param recycler The Recycler that can be used to convert view positions into adapter 6715 * positions 6716 * @param state The current state of RecyclerView 6717 * @return True if layout is hierarchical. 6718 */ isLayoutHierarchical(Recycler recycler, State state)6719 public boolean isLayoutHierarchical(Recycler recycler, State state) { 6720 return false; 6721 } 6722 6723 // called by accessibility delegate performAccessibilityAction(int action, Bundle args)6724 boolean performAccessibilityAction(int action, Bundle args) { 6725 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, 6726 action, args); 6727 } 6728 6729 /** 6730 * Called by AccessibilityDelegate when an action is requested from the RecyclerView. 6731 * 6732 * @param recycler The Recycler that can be used to convert view positions into adapter 6733 * positions 6734 * @param state The current state of RecyclerView 6735 * @param action The action to perform 6736 * @param args Optional action arguments 6737 * @see View#performAccessibilityAction(int, android.os.Bundle) 6738 */ performAccessibilityAction(Recycler recycler, State state, int action, Bundle args)6739 public boolean performAccessibilityAction(Recycler recycler, State state, int action, 6740 Bundle args) { 6741 if (mRecyclerView == null) { 6742 return false; 6743 } 6744 int vScroll = 0, hScroll = 0; 6745 switch (action) { 6746 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 6747 if (ViewCompat.canScrollVertically(mRecyclerView, -1)) { 6748 vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); 6749 } 6750 if (ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { 6751 hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); 6752 } 6753 break; 6754 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 6755 if (ViewCompat.canScrollVertically(mRecyclerView, 1)) { 6756 vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); 6757 } 6758 if (ViewCompat.canScrollHorizontally(mRecyclerView, 1)) { 6759 hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); 6760 } 6761 break; 6762 } 6763 if (vScroll == 0 && hScroll == 0) { 6764 return false; 6765 } 6766 mRecyclerView.scrollBy(hScroll, vScroll); 6767 return true; 6768 } 6769 6770 // called by accessibility delegate performAccessibilityActionForItem(View view, int action, Bundle args)6771 boolean performAccessibilityActionForItem(View view, int action, Bundle args) { 6772 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, 6773 view, action, args); 6774 } 6775 6776 /** 6777 * Called by AccessibilityDelegate when an accessibility action is requested on one of the 6778 * children of LayoutManager. 6779 * <p> 6780 * Default implementation does not do anything. 6781 * 6782 * @param recycler The Recycler that can be used to convert view positions into adapter 6783 * positions 6784 * @param state The current state of RecyclerView 6785 * @param view The child view on which the action is performed 6786 * @param action The action to perform 6787 * @param args Optional action arguments 6788 * @return true if action is handled 6789 * @see View#performAccessibilityAction(int, android.os.Bundle) 6790 */ performAccessibilityActionForItem(Recycler recycler, State state, View view, int action, Bundle args)6791 public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view, 6792 int action, Bundle args) { 6793 return false; 6794 } 6795 } 6796 removeFromDisappearingList(View child)6797 private void removeFromDisappearingList(View child) { 6798 mDisappearingViewsInLayoutPass.remove(child); 6799 } 6800 addToDisappearingList(View child)6801 private void addToDisappearingList(View child) { 6802 if (!mDisappearingViewsInLayoutPass.contains(child)) { 6803 mDisappearingViewsInLayoutPass.add(child); 6804 } 6805 } 6806 6807 /** 6808 * An ItemDecoration allows the application to add a special drawing and layout offset 6809 * to specific item views from the adapter's data set. This can be useful for drawing dividers 6810 * between items, highlights, visual grouping boundaries and more. 6811 * 6812 * <p>All ItemDecorations are drawn in the order they were added, before the item 6813 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} 6814 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, 6815 * RecyclerView.State)}.</p> 6816 */ 6817 public static abstract class ItemDecoration { 6818 /** 6819 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 6820 * Any content drawn by this method will be drawn before the item views are drawn, 6821 * and will thus appear underneath the views. 6822 * 6823 * @param c Canvas to draw into 6824 * @param parent RecyclerView this ItemDecoration is drawing into 6825 * @param state The current state of RecyclerView 6826 */ onDraw(Canvas c, RecyclerView parent, State state)6827 public void onDraw(Canvas c, RecyclerView parent, State state) { 6828 onDraw(c, parent); 6829 } 6830 6831 /** 6832 * @deprecated 6833 * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} 6834 */ 6835 @Deprecated onDraw(Canvas c, RecyclerView parent)6836 public void onDraw(Canvas c, RecyclerView parent) { 6837 } 6838 6839 /** 6840 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 6841 * Any content drawn by this method will be drawn after the item views are drawn 6842 * and will thus appear over the views. 6843 * 6844 * @param c Canvas to draw into 6845 * @param parent RecyclerView this ItemDecoration is drawing into 6846 * @param state The current state of RecyclerView. 6847 */ onDrawOver(Canvas c, RecyclerView parent, State state)6848 public void onDrawOver(Canvas c, RecyclerView parent, State state) { 6849 onDrawOver(c, parent); 6850 } 6851 6852 /** 6853 * @deprecated 6854 * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} 6855 */ 6856 @Deprecated onDrawOver(Canvas c, RecyclerView parent)6857 public void onDrawOver(Canvas c, RecyclerView parent) { 6858 } 6859 6860 6861 /** 6862 * @deprecated 6863 * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} 6864 */ 6865 @Deprecated getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)6866 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 6867 outRect.set(0, 0, 0, 0); 6868 } 6869 6870 /** 6871 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 6872 * the number of pixels that the item view should be inset by, similar to padding or margin. 6873 * The default implementation sets the bounds of outRect to 0 and returns. 6874 * 6875 * <p> 6876 * If this ItemDecoration does not affect the positioning of item views, it should set 6877 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 6878 * before returning. 6879 * 6880 * <p> 6881 * If you need to access Adapter for additional data, you can call 6882 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the 6883 * View. 6884 * 6885 * @param outRect Rect to receive the output. 6886 * @param view The child view to decorate 6887 * @param parent RecyclerView this ItemDecoration is decorating 6888 * @param state The current state of RecyclerView. 6889 */ getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)6890 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { 6891 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), 6892 parent); 6893 } 6894 } 6895 6896 /** 6897 * An OnItemTouchListener allows the application to intercept touch events in progress at the 6898 * view hierarchy level of the RecyclerView before those touch events are considered for 6899 * RecyclerView's own scrolling behavior. 6900 * 6901 * <p>This can be useful for applications that wish to implement various forms of gestural 6902 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 6903 * a touch interaction already in progress even if the RecyclerView is already handling that 6904 * gesture stream itself for the purposes of scrolling.</p> 6905 */ 6906 public interface OnItemTouchListener { 6907 /** 6908 * Silently observe and/or take over touch events sent to the RecyclerView 6909 * before they are handled by either the RecyclerView itself or its child views. 6910 * 6911 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 6912 * in the order in which each listener was added, before any other touch processing 6913 * by the RecyclerView itself or child views occurs.</p> 6914 * 6915 * @param e MotionEvent describing the touch event. All coordinates are in 6916 * the RecyclerView's coordinate system. 6917 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 6918 * to continue with the current behavior and continue observing future events in 6919 * the gesture. 6920 */ onInterceptTouchEvent(RecyclerView rv, MotionEvent e)6921 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e); 6922 6923 /** 6924 * Process a touch event as part of a gesture that was claimed by returning true from 6925 * a previous call to {@link #onInterceptTouchEvent}. 6926 * 6927 * @param e MotionEvent describing the touch event. All coordinates are in 6928 * the RecyclerView's coordinate system. 6929 */ onTouchEvent(RecyclerView rv, MotionEvent e)6930 public void onTouchEvent(RecyclerView rv, MotionEvent e); 6931 } 6932 6933 /** 6934 * An OnScrollListener can be set on a RecyclerView to receive messages 6935 * when a scrolling event has occurred on that RecyclerView. 6936 * 6937 * @see RecyclerView#setOnScrollListener(OnScrollListener) 6938 */ 6939 abstract static public class OnScrollListener { 6940 /** 6941 * Callback method to be invoked when RecyclerView's scroll state changes. 6942 * 6943 * @param recyclerView The RecyclerView whose scroll state has changed. 6944 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, 6945 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 6946 */ onScrollStateChanged(RecyclerView recyclerView, int newState)6947 public void onScrollStateChanged(RecyclerView recyclerView, int newState){} 6948 6949 /** 6950 * Callback method to be invoked when the RecyclerView has been scrolled. This will be 6951 * called after the scroll has completed. 6952 * <p> 6953 * This callback will also be called if visible item range changes after a layout 6954 * calculation. In that case, dx and dy will be 0. 6955 * 6956 * @param recyclerView The RecyclerView which scrolled. 6957 * @param dx The amount of horizontal scroll. 6958 * @param dy The amount of vertical scroll. 6959 */ onScrolled(RecyclerView recyclerView, int dx, int dy)6960 public void onScrolled(RecyclerView recyclerView, int dx, int dy){} 6961 } 6962 6963 /** 6964 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 6965 * a view is recycled. 6966 * 6967 * @see RecyclerView#setRecyclerListener(RecyclerListener) 6968 */ 6969 public interface RecyclerListener { 6970 6971 /** 6972 * This method is called whenever the view in the ViewHolder is recycled. 6973 * 6974 * @param holder The ViewHolder containing the view that was recycled 6975 */ onViewRecycled(ViewHolder holder)6976 public void onViewRecycled(ViewHolder holder); 6977 } 6978 6979 /** 6980 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 6981 * 6982 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 6983 * potentially expensive {@link View#findViewById(int)} results.</p> 6984 * 6985 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 6986 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 6987 * their own custom ViewHolder implementations to store data that makes binding view contents 6988 * easier. Implementations should assume that individual item views will hold strong references 6989 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 6990 * strong references to extra off-screen item views for caching purposes</p> 6991 */ 6992 public static abstract class ViewHolder { 6993 public final View itemView; 6994 int mPosition = NO_POSITION; 6995 int mOldPosition = NO_POSITION; 6996 long mItemId = NO_ID; 6997 int mItemViewType = INVALID_TYPE; 6998 int mPreLayoutPosition = NO_POSITION; 6999 7000 // The item that this holder is shadowing during an item change event/animation 7001 ViewHolder mShadowedHolder = null; 7002 // The item that is shadowing this holder during an item change event/animation 7003 ViewHolder mShadowingHolder = null; 7004 7005 /** 7006 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 7007 * are all valid. 7008 */ 7009 static final int FLAG_BOUND = 1 << 0; 7010 7011 /** 7012 * The data this ViewHolder's view reflects is stale and needs to be rebound 7013 * by the adapter. mPosition and mItemId are consistent. 7014 */ 7015 static final int FLAG_UPDATE = 1 << 1; 7016 7017 /** 7018 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 7019 * are not to be trusted and may no longer match the item view type. 7020 * This ViewHolder must be fully rebound to different data. 7021 */ 7022 static final int FLAG_INVALID = 1 << 2; 7023 7024 /** 7025 * This ViewHolder points at data that represents an item previously removed from the 7026 * data set. Its view may still be used for things like outgoing animations. 7027 */ 7028 static final int FLAG_REMOVED = 1 << 3; 7029 7030 /** 7031 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 7032 * and is intended to keep views around during animations. 7033 */ 7034 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 7035 7036 /** 7037 * This ViewHolder is returned from scrap which means we are expecting an addView call 7038 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until 7039 * the end of the layout pass and then recycled by RecyclerView if it is not added back to 7040 * the RecyclerView. 7041 */ 7042 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; 7043 7044 /** 7045 * This ViewHolder's contents have changed. This flag is used as an indication that 7046 * change animations may be used, if supported by the ItemAnimator. 7047 */ 7048 static final int FLAG_CHANGED = 1 << 6; 7049 7050 /** 7051 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove 7052 * it unless LayoutManager is replaced. 7053 * It is still fully visible to the LayoutManager. 7054 */ 7055 static final int FLAG_IGNORE = 1 << 7; 7056 7057 /** 7058 * When the View is detached form the parent, we set this flag so that we can take correct 7059 * action when we need to remove it or add it back. 7060 */ 7061 static final int FLAG_TMP_DETACHED = 1 << 8; 7062 7063 /** 7064 * Set when we can no longer determine the adapter position of this ViewHolder until it is 7065 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is 7066 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon 7067 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is 7068 * re-calculated. 7069 */ 7070 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; 7071 7072 private int mFlags; 7073 7074 private int mIsRecyclableCount = 0; 7075 7076 // If non-null, view is currently considered scrap and may be reused for other data by the 7077 // scrap container. 7078 private Recycler mScrapContainer = null; 7079 ViewHolder(View itemView)7080 public ViewHolder(View itemView) { 7081 if (itemView == null) { 7082 throw new IllegalArgumentException("itemView may not be null"); 7083 } 7084 this.itemView = itemView; 7085 } 7086 flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout)7087 void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { 7088 addFlags(ViewHolder.FLAG_REMOVED); 7089 offsetPosition(offset, applyToPreLayout); 7090 mPosition = mNewPosition; 7091 } 7092 offsetPosition(int offset, boolean applyToPreLayout)7093 void offsetPosition(int offset, boolean applyToPreLayout) { 7094 if (mOldPosition == NO_POSITION) { 7095 mOldPosition = mPosition; 7096 } 7097 if (mPreLayoutPosition == NO_POSITION) { 7098 mPreLayoutPosition = mPosition; 7099 } 7100 if (applyToPreLayout) { 7101 mPreLayoutPosition += offset; 7102 } 7103 mPosition += offset; 7104 if (itemView.getLayoutParams() != null) { 7105 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; 7106 } 7107 } 7108 clearOldPosition()7109 void clearOldPosition() { 7110 mOldPosition = NO_POSITION; 7111 mPreLayoutPosition = NO_POSITION; 7112 } 7113 saveOldPosition()7114 void saveOldPosition() { 7115 if (mOldPosition == NO_POSITION) { 7116 mOldPosition = mPosition; 7117 } 7118 } 7119 shouldIgnore()7120 boolean shouldIgnore() { 7121 return (mFlags & FLAG_IGNORE) != 0; 7122 } 7123 7124 /** 7125 * @deprecated This method is deprecated because its meaning is ambiguous due to the async 7126 * handling of adapter updates. Please use {@link #getLayoutPosition()} or 7127 * {@link #getAdapterPosition()} depending on your use case. 7128 * 7129 * @see #getLayoutPosition() 7130 * @see #getAdapterPosition() 7131 */ 7132 @Deprecated getPosition()7133 public final int getPosition() { 7134 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 7135 } 7136 7137 /** 7138 * Returns the position of the ViewHolder in terms of the latest layout pass. 7139 * <p> 7140 * This position is mostly used by RecyclerView components to be consistent while 7141 * RecyclerView lazily processes adapter updates. 7142 * <p> 7143 * For performance and animation reasons, RecyclerView batches all adapter updates until the 7144 * next layout pass. This may cause mismatches between the Adapter position of the item and 7145 * the position it had in the latest layout calculations. 7146 * <p> 7147 * LayoutManagers should always call this method while doing calculations based on item 7148 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, 7149 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position 7150 * of the item. 7151 * <p> 7152 * If LayoutManager needs to call an external method that requires the adapter position of 7153 * the item, it can use {@link #getAdapterPosition()} or 7154 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. 7155 * 7156 * @return Returns the adapter position of the ViewHolder in the latest layout pass. 7157 * @see #getAdapterPosition() 7158 */ getLayoutPosition()7159 public final int getLayoutPosition() { 7160 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 7161 } 7162 7163 /** 7164 * Returns the Adapter position of the item represented by this ViewHolder. 7165 * <p> 7166 * Note that this might be different than the {@link #getLayoutPosition()} if there are 7167 * pending adapter updates but a new layout pass has not happened yet. 7168 * <p> 7169 * RecyclerView does not handle any adapter updates until the next layout traversal. This 7170 * may create temporary inconsistencies between what user sees on the screen and what 7171 * adapter contents have. This inconsistency is not important since it will be less than 7172 * 16ms but it might be a problem if you want to use ViewHolder position to access the 7173 * adapter. Sometimes, you may need to get the exact adapter position to do 7174 * some actions in response to user events. In that case, you should use this method which 7175 * will calculate the Adapter position of the ViewHolder. 7176 * <p> 7177 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 7178 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 7179 * 7180 * @return The adapter position of the item if it still exists in the adapter. 7181 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 7182 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 7183 * layout pass or the ViewHolder has been removed from the RecyclerView. 7184 */ getAdapterPosition()7185 public final int getAdapterPosition() { 7186 final ViewParent parent = itemView.getParent(); 7187 if (!(parent instanceof RecyclerView)) { 7188 return -1; 7189 } 7190 final RecyclerView rv = (RecyclerView) parent; 7191 return rv.getAdapterPositionFor(this); 7192 } 7193 7194 /** 7195 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders 7196 * to perform animations. 7197 * <p> 7198 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its 7199 * adapter index in the previous layout. 7200 * 7201 * @return The previous adapter index of the Item represented by this ViewHolder or 7202 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is 7203 * complete). 7204 */ getOldPosition()7205 public final int getOldPosition() { 7206 return mOldPosition; 7207 } 7208 7209 /** 7210 * Returns The itemId represented by this ViewHolder. 7211 * 7212 * @return The the item's id if adapter has stable ids, {@link RecyclerView#NO_ID} 7213 * otherwise 7214 */ getItemId()7215 public final long getItemId() { 7216 return mItemId; 7217 } 7218 7219 /** 7220 * @return The view type of this ViewHolder. 7221 */ getItemViewType()7222 public final int getItemViewType() { 7223 return mItemViewType; 7224 } 7225 isScrap()7226 boolean isScrap() { 7227 return mScrapContainer != null; 7228 } 7229 unScrap()7230 void unScrap() { 7231 mScrapContainer.unscrapView(this); 7232 } 7233 wasReturnedFromScrap()7234 boolean wasReturnedFromScrap() { 7235 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; 7236 } 7237 clearReturnedFromScrapFlag()7238 void clearReturnedFromScrapFlag() { 7239 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; 7240 } 7241 clearTmpDetachFlag()7242 void clearTmpDetachFlag() { 7243 mFlags = mFlags & ~FLAG_TMP_DETACHED; 7244 } 7245 stopIgnoring()7246 void stopIgnoring() { 7247 mFlags = mFlags & ~FLAG_IGNORE; 7248 } 7249 setScrapContainer(Recycler recycler)7250 void setScrapContainer(Recycler recycler) { 7251 mScrapContainer = recycler; 7252 } 7253 isInvalid()7254 boolean isInvalid() { 7255 return (mFlags & FLAG_INVALID) != 0; 7256 } 7257 needsUpdate()7258 boolean needsUpdate() { 7259 return (mFlags & FLAG_UPDATE) != 0; 7260 } 7261 isChanged()7262 boolean isChanged() { 7263 return (mFlags & FLAG_CHANGED) != 0; 7264 } 7265 isBound()7266 boolean isBound() { 7267 return (mFlags & FLAG_BOUND) != 0; 7268 } 7269 isRemoved()7270 boolean isRemoved() { 7271 return (mFlags & FLAG_REMOVED) != 0; 7272 } 7273 hasAnyOfTheFlags(int flags)7274 boolean hasAnyOfTheFlags(int flags) { 7275 return (mFlags & flags) != 0; 7276 } 7277 isTmpDetached()7278 boolean isTmpDetached() { 7279 return (mFlags & FLAG_TMP_DETACHED) != 0; 7280 } 7281 isAdapterPositionUnknown()7282 boolean isAdapterPositionUnknown() { 7283 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0; 7284 } 7285 setFlags(int flags, int mask)7286 void setFlags(int flags, int mask) { 7287 mFlags = (mFlags & ~mask) | (flags & mask); 7288 } 7289 addFlags(int flags)7290 void addFlags(int flags) { 7291 mFlags |= flags; 7292 } 7293 resetInternal()7294 void resetInternal() { 7295 mFlags = 0; 7296 mPosition = NO_POSITION; 7297 mOldPosition = NO_POSITION; 7298 mItemId = NO_ID; 7299 mPreLayoutPosition = NO_POSITION; 7300 mIsRecyclableCount = 0; 7301 mShadowedHolder = null; 7302 mShadowingHolder = null; 7303 } 7304 7305 @Override toString()7306 public String toString() { 7307 final StringBuilder sb = new StringBuilder("ViewHolder{" + 7308 Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId + 7309 ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); 7310 if (isScrap()) sb.append(" scrap"); 7311 if (isInvalid()) sb.append(" invalid"); 7312 if (!isBound()) sb.append(" unbound"); 7313 if (needsUpdate()) sb.append(" update"); 7314 if (isRemoved()) sb.append(" removed"); 7315 if (shouldIgnore()) sb.append(" ignored"); 7316 if (isChanged()) sb.append(" changed"); 7317 if (isTmpDetached()) sb.append(" tmpDetached"); 7318 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); 7319 if (!isAdapterPositionUnknown()) sb.append("undefined adapter position"); 7320 7321 if (itemView.getParent() == null) sb.append(" no parent"); 7322 sb.append("}"); 7323 return sb.toString(); 7324 } 7325 7326 /** 7327 * Informs the recycler whether this item can be recycled. Views which are not 7328 * recyclable will not be reused for other items until setIsRecyclable() is 7329 * later set to true. Calls to setIsRecyclable() should always be paired (one 7330 * call to setIsRecyclabe(false) should always be matched with a later call to 7331 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally 7332 * reference-counted. 7333 * 7334 * @param recyclable Whether this item is available to be recycled. Default value 7335 * is true. 7336 */ setIsRecyclable(boolean recyclable)7337 public final void setIsRecyclable(boolean recyclable) { 7338 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; 7339 if (mIsRecyclableCount < 0) { 7340 mIsRecyclableCount = 0; 7341 if (DEBUG) { 7342 throw new RuntimeException("isRecyclable decremented below 0: " + 7343 "unmatched pair of setIsRecyable() calls for " + this); 7344 } 7345 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " + 7346 "unmatched pair of setIsRecyable() calls for " + this); 7347 } else if (!recyclable && mIsRecyclableCount == 1) { 7348 mFlags |= FLAG_NOT_RECYCLABLE; 7349 } else if (recyclable && mIsRecyclableCount == 0) { 7350 mFlags &= ~FLAG_NOT_RECYCLABLE; 7351 } 7352 if (DEBUG) { 7353 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); 7354 } 7355 } 7356 7357 /** 7358 * @see {@link #setIsRecyclable(boolean)} 7359 * 7360 * @return true if this item is available to be recycled, false otherwise. 7361 */ isRecyclable()7362 public final boolean isRecyclable() { 7363 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && 7364 !ViewCompat.hasTransientState(itemView); 7365 } 7366 7367 /** 7368 * Returns whether we have animations referring to this view holder or not. 7369 * This is similar to isRecyclable flag but does not check transient state. 7370 */ shouldBeKeptAsChild()7371 private boolean shouldBeKeptAsChild() { 7372 return (mFlags & FLAG_NOT_RECYCLABLE) != 0; 7373 } 7374 7375 /** 7376 * @return True if ViewHolder is not refenrenced by RecyclerView animations but has 7377 * transient state which will prevent it from being recycled. 7378 */ doesTransientStatePreventRecycling()7379 private boolean doesTransientStatePreventRecycling() { 7380 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); 7381 } 7382 } 7383 getAdapterPositionFor(ViewHolder viewHolder)7384 private int getAdapterPositionFor(ViewHolder viewHolder) { 7385 if (viewHolder.hasAnyOfTheFlags( 7386 ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { 7387 return RecyclerView.NO_POSITION; 7388 } 7389 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); 7390 } 7391 7392 /** 7393 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 7394 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 7395 * to create their own subclass of this <code>LayoutParams</code> class 7396 * to store any additional required per-child view metadata about the layout. 7397 */ 7398 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { 7399 ViewHolder mViewHolder; 7400 final Rect mDecorInsets = new Rect(); 7401 boolean mInsetsDirty = true; 7402 // Flag is set to true if the view is bound while it is detached from RV. 7403 // In this case, we need to manually call invalidate after view is added to guarantee that 7404 // invalidation is populated through the View hierarchy 7405 boolean mPendingInvalidate = false; 7406 LayoutParams(Context c, AttributeSet attrs)7407 public LayoutParams(Context c, AttributeSet attrs) { 7408 super(c, attrs); 7409 } 7410 LayoutParams(int width, int height)7411 public LayoutParams(int width, int height) { 7412 super(width, height); 7413 } 7414 LayoutParams(MarginLayoutParams source)7415 public LayoutParams(MarginLayoutParams source) { 7416 super(source); 7417 } 7418 LayoutParams(ViewGroup.LayoutParams source)7419 public LayoutParams(ViewGroup.LayoutParams source) { 7420 super(source); 7421 } 7422 LayoutParams(LayoutParams source)7423 public LayoutParams(LayoutParams source) { 7424 super((ViewGroup.LayoutParams) source); 7425 } 7426 7427 /** 7428 * Returns true if the view this LayoutParams is attached to needs to have its content 7429 * updated from the corresponding adapter. 7430 * 7431 * @return true if the view should have its content updated 7432 */ viewNeedsUpdate()7433 public boolean viewNeedsUpdate() { 7434 return mViewHolder.needsUpdate(); 7435 } 7436 7437 /** 7438 * Returns true if the view this LayoutParams is attached to is now representing 7439 * potentially invalid data. A LayoutManager should scrap/recycle it. 7440 * 7441 * @return true if the view is invalid 7442 */ isViewInvalid()7443 public boolean isViewInvalid() { 7444 return mViewHolder.isInvalid(); 7445 } 7446 7447 /** 7448 * Returns true if the adapter data item corresponding to the view this LayoutParams 7449 * is attached to has been removed from the data set. A LayoutManager may choose to 7450 * treat it differently in order to animate its outgoing or disappearing state. 7451 * 7452 * @return true if the item the view corresponds to was removed from the data set 7453 */ isItemRemoved()7454 public boolean isItemRemoved() { 7455 return mViewHolder.isRemoved(); 7456 } 7457 7458 /** 7459 * Returns true if the adapter data item corresponding to the view this LayoutParams 7460 * is attached to has been changed in the data set. A LayoutManager may choose to 7461 * treat it differently in order to animate its changing state. 7462 * 7463 * @return true if the item the view corresponds to was changed in the data set 7464 */ isItemChanged()7465 public boolean isItemChanged() { 7466 return mViewHolder.isChanged(); 7467 } 7468 7469 /** 7470 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} 7471 */ getViewPosition()7472 public int getViewPosition() { 7473 return mViewHolder.getPosition(); 7474 } 7475 7476 /** 7477 * Returns the adapter position that the view this LayoutParams is attached to corresponds 7478 * to as of latest layout calculation. 7479 * 7480 * @return the adapter position this view as of latest layout pass 7481 */ getViewLayoutPosition()7482 public int getViewLayoutPosition() { 7483 return mViewHolder.getLayoutPosition(); 7484 } 7485 7486 /** 7487 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 7488 * corresponds to. 7489 * 7490 * @return the up-to-date adapter position this view. It may return 7491 * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or 7492 * its up-to-date position cannot be calculated. 7493 */ getViewAdapterPosition()7494 public int getViewAdapterPosition() { 7495 return mViewHolder.getAdapterPosition(); 7496 } 7497 } 7498 7499 /** 7500 * Observer base class for watching changes to an {@link Adapter}. 7501 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 7502 */ 7503 public static abstract class AdapterDataObserver { onChanged()7504 public void onChanged() { 7505 // Do nothing 7506 } 7507 onItemRangeChanged(int positionStart, int itemCount)7508 public void onItemRangeChanged(int positionStart, int itemCount) { 7509 // do nothing 7510 } 7511 onItemRangeInserted(int positionStart, int itemCount)7512 public void onItemRangeInserted(int positionStart, int itemCount) { 7513 // do nothing 7514 } 7515 onItemRangeRemoved(int positionStart, int itemCount)7516 public void onItemRangeRemoved(int positionStart, int itemCount) { 7517 // do nothing 7518 } 7519 onItemRangeMoved(int fromPosition, int toPosition, int itemCount)7520 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 7521 // do nothing 7522 } 7523 } 7524 7525 /** 7526 * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and 7527 * provides methods to trigger a programmatic scroll.</p> 7528 * 7529 * @see LinearSmoothScroller 7530 */ 7531 public static abstract class SmoothScroller { 7532 7533 private int mTargetPosition = RecyclerView.NO_POSITION; 7534 7535 private RecyclerView mRecyclerView; 7536 7537 private LayoutManager mLayoutManager; 7538 7539 private boolean mPendingInitialRun; 7540 7541 private boolean mRunning; 7542 7543 private View mTargetView; 7544 7545 private final Action mRecyclingAction; 7546 SmoothScroller()7547 public SmoothScroller() { 7548 mRecyclingAction = new Action(0, 0); 7549 } 7550 7551 /** 7552 * Starts a smooth scroll for the given target position. 7553 * <p>In each animation step, {@link RecyclerView} will check 7554 * for the target view and call either 7555 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 7556 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until 7557 * SmoothScroller is stopped.</p> 7558 * 7559 * <p>Note that if RecyclerView finds the target view, it will automatically stop the 7560 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will 7561 * stop calling SmoothScroller in each animation step.</p> 7562 */ start(RecyclerView recyclerView, LayoutManager layoutManager)7563 void start(RecyclerView recyclerView, LayoutManager layoutManager) { 7564 mRecyclerView = recyclerView; 7565 mLayoutManager = layoutManager; 7566 if (mTargetPosition == RecyclerView.NO_POSITION) { 7567 throw new IllegalArgumentException("Invalid target position"); 7568 } 7569 mRecyclerView.mState.mTargetPosition = mTargetPosition; 7570 mRunning = true; 7571 mPendingInitialRun = true; 7572 mTargetView = findViewByPosition(getTargetPosition()); 7573 onStart(); 7574 mRecyclerView.mViewFlinger.postOnAnimation(); 7575 } 7576 setTargetPosition(int targetPosition)7577 public void setTargetPosition(int targetPosition) { 7578 mTargetPosition = targetPosition; 7579 } 7580 7581 /** 7582 * @return The LayoutManager to which this SmoothScroller is attached 7583 */ getLayoutManager()7584 public LayoutManager getLayoutManager() { 7585 return mLayoutManager; 7586 } 7587 7588 /** 7589 * Stops running the SmoothScroller in each animation callback. Note that this does not 7590 * cancel any existing {@link Action} updated by 7591 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 7592 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. 7593 */ stop()7594 final protected void stop() { 7595 if (!mRunning) { 7596 return; 7597 } 7598 onStop(); 7599 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; 7600 mTargetView = null; 7601 mTargetPosition = RecyclerView.NO_POSITION; 7602 mPendingInitialRun = false; 7603 mRunning = false; 7604 // trigger a cleanup 7605 mLayoutManager.onSmoothScrollerStopped(this); 7606 // clear references to avoid any potential leak by a custom smooth scroller 7607 mLayoutManager = null; 7608 mRecyclerView = null; 7609 } 7610 7611 /** 7612 * Returns true if SmoothScroller has been started but has not received the first 7613 * animation 7614 * callback yet. 7615 * 7616 * @return True if this SmoothScroller is waiting to start 7617 */ isPendingInitialRun()7618 public boolean isPendingInitialRun() { 7619 return mPendingInitialRun; 7620 } 7621 7622 7623 /** 7624 * @return True if SmoothScroller is currently active 7625 */ isRunning()7626 public boolean isRunning() { 7627 return mRunning; 7628 } 7629 7630 /** 7631 * Returns the adapter position of the target item 7632 * 7633 * @return Adapter position of the target item or 7634 * {@link RecyclerView#NO_POSITION} if no target view is set. 7635 */ getTargetPosition()7636 public int getTargetPosition() { 7637 return mTargetPosition; 7638 } 7639 onAnimation(int dx, int dy)7640 private void onAnimation(int dx, int dy) { 7641 if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) { 7642 stop(); 7643 } 7644 mPendingInitialRun = false; 7645 if (mTargetView != null) { 7646 // verify target position 7647 if (getChildPosition(mTargetView) == mTargetPosition) { 7648 onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction); 7649 mRecyclingAction.runIfNecessary(mRecyclerView); 7650 stop(); 7651 } else { 7652 Log.e(TAG, "Passed over target position while smooth scrolling."); 7653 mTargetView = null; 7654 } 7655 } 7656 if (mRunning) { 7657 onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction); 7658 mRecyclingAction.runIfNecessary(mRecyclerView); 7659 } 7660 } 7661 7662 /** 7663 * @see RecyclerView#getChildLayoutPosition(android.view.View) 7664 */ getChildPosition(View view)7665 public int getChildPosition(View view) { 7666 return mRecyclerView.getChildLayoutPosition(view); 7667 } 7668 7669 /** 7670 * @see RecyclerView.LayoutManager#getChildCount() 7671 */ getChildCount()7672 public int getChildCount() { 7673 return mRecyclerView.mLayout.getChildCount(); 7674 } 7675 7676 /** 7677 * @see RecyclerView.LayoutManager#findViewByPosition(int) 7678 */ findViewByPosition(int position)7679 public View findViewByPosition(int position) { 7680 return mRecyclerView.mLayout.findViewByPosition(position); 7681 } 7682 7683 /** 7684 * @see RecyclerView#scrollToPosition(int) 7685 */ instantScrollToPosition(int position)7686 public void instantScrollToPosition(int position) { 7687 mRecyclerView.scrollToPosition(position); 7688 } 7689 onChildAttachedToWindow(View child)7690 protected void onChildAttachedToWindow(View child) { 7691 if (getChildPosition(child) == getTargetPosition()) { 7692 mTargetView = child; 7693 if (DEBUG) { 7694 Log.d(TAG, "smooth scroll target view has been attached"); 7695 } 7696 } 7697 } 7698 7699 /** 7700 * Normalizes the vector. 7701 * @param scrollVector The vector that points to the target scroll position 7702 */ normalize(PointF scrollVector)7703 protected void normalize(PointF scrollVector) { 7704 final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y * 7705 scrollVector.y); 7706 scrollVector.x /= magnitute; 7707 scrollVector.y /= magnitute; 7708 } 7709 7710 /** 7711 * Called when smooth scroll is started. This might be a good time to do setup. 7712 */ onStart()7713 abstract protected void onStart(); 7714 7715 /** 7716 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. 7717 * @see #stop() 7718 */ onStop()7719 abstract protected void onStop(); 7720 7721 /** 7722 * <p>RecyclerView will call this method each time it scrolls until it can find the target 7723 * position in the layout.</p> 7724 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the 7725 * provided {@link Action} to define the next scroll.</p> 7726 * 7727 * @param dx Last scroll amount horizontally 7728 * @param dy Last scroll amount verticaully 7729 * @param state Transient state of RecyclerView 7730 * @param action If you want to trigger a new smooth scroll and cancel the previous one, 7731 * update this object. 7732 */ onSeekTargetStep(int dx, int dy, State state, Action action)7733 abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action); 7734 7735 /** 7736 * Called when the target position is laid out. This is the last callback SmoothScroller 7737 * will receive and it should update the provided {@link Action} to define the scroll 7738 * details towards the target view. 7739 * @param targetView The view element which render the target position. 7740 * @param state Transient state of RecyclerView 7741 * @param action Action instance that you should update to define final scroll action 7742 * towards the targetView 7743 */ onTargetFound(View targetView, State state, Action action)7744 abstract protected void onTargetFound(View targetView, State state, Action action); 7745 7746 /** 7747 * Holds information about a smooth scroll request by a {@link SmoothScroller}. 7748 */ 7749 public static class Action { 7750 7751 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; 7752 7753 private int mDx; 7754 7755 private int mDy; 7756 7757 private int mDuration; 7758 7759 private Interpolator mInterpolator; 7760 7761 private boolean changed = false; 7762 7763 // we track this variable to inform custom implementer if they are updating the action 7764 // in every animation callback 7765 private int consecutiveUpdates = 0; 7766 7767 /** 7768 * @param dx Pixels to scroll horizontally 7769 * @param dy Pixels to scroll vertically 7770 */ Action(int dx, int dy)7771 public Action(int dx, int dy) { 7772 this(dx, dy, UNDEFINED_DURATION, null); 7773 } 7774 7775 /** 7776 * @param dx Pixels to scroll horizontally 7777 * @param dy Pixels to scroll vertically 7778 * @param duration Duration of the animation in milliseconds 7779 */ Action(int dx, int dy, int duration)7780 public Action(int dx, int dy, int duration) { 7781 this(dx, dy, duration, null); 7782 } 7783 7784 /** 7785 * @param dx Pixels to scroll horizontally 7786 * @param dy Pixels to scroll vertically 7787 * @param duration Duration of the animation in milliseconds 7788 * @param interpolator Interpolator to be used when calculating scroll position in each 7789 * animation step 7790 */ Action(int dx, int dy, int duration, Interpolator interpolator)7791 public Action(int dx, int dy, int duration, Interpolator interpolator) { 7792 mDx = dx; 7793 mDy = dy; 7794 mDuration = duration; 7795 mInterpolator = interpolator; 7796 } runIfNecessary(RecyclerView recyclerView)7797 private void runIfNecessary(RecyclerView recyclerView) { 7798 if (changed) { 7799 validate(); 7800 if (mInterpolator == null) { 7801 if (mDuration == UNDEFINED_DURATION) { 7802 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); 7803 } else { 7804 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); 7805 } 7806 } else { 7807 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator); 7808 } 7809 consecutiveUpdates ++; 7810 if (consecutiveUpdates > 10) { 7811 // A new action is being set in every animation step. This looks like a bad 7812 // implementation. Inform developer. 7813 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" 7814 + " you are not changing it unless necessary"); 7815 } 7816 changed = false; 7817 } else { 7818 consecutiveUpdates = 0; 7819 } 7820 } 7821 validate()7822 private void validate() { 7823 if (mInterpolator != null && mDuration < 1) { 7824 throw new IllegalStateException("If you provide an interpolator, you must" 7825 + " set a positive duration"); 7826 } else if (mDuration < 1) { 7827 throw new IllegalStateException("Scroll duration must be a positive number"); 7828 } 7829 } 7830 getDx()7831 public int getDx() { 7832 return mDx; 7833 } 7834 setDx(int dx)7835 public void setDx(int dx) { 7836 changed = true; 7837 mDx = dx; 7838 } 7839 getDy()7840 public int getDy() { 7841 return mDy; 7842 } 7843 setDy(int dy)7844 public void setDy(int dy) { 7845 changed = true; 7846 mDy = dy; 7847 } 7848 getDuration()7849 public int getDuration() { 7850 return mDuration; 7851 } 7852 setDuration(int duration)7853 public void setDuration(int duration) { 7854 changed = true; 7855 mDuration = duration; 7856 } 7857 getInterpolator()7858 public Interpolator getInterpolator() { 7859 return mInterpolator; 7860 } 7861 7862 /** 7863 * Sets the interpolator to calculate scroll steps 7864 * @param interpolator The interpolator to use. If you specify an interpolator, you must 7865 * also set the duration. 7866 * @see #setDuration(int) 7867 */ setInterpolator(Interpolator interpolator)7868 public void setInterpolator(Interpolator interpolator) { 7869 changed = true; 7870 mInterpolator = interpolator; 7871 } 7872 7873 /** 7874 * Updates the action with given parameters. 7875 * @param dx Pixels to scroll horizontally 7876 * @param dy Pixels to scroll vertically 7877 * @param duration Duration of the animation in milliseconds 7878 * @param interpolator Interpolator to be used when calculating scroll position in each 7879 * animation step 7880 */ update(int dx, int dy, int duration, Interpolator interpolator)7881 public void update(int dx, int dy, int duration, Interpolator interpolator) { 7882 mDx = dx; 7883 mDy = dy; 7884 mDuration = duration; 7885 mInterpolator = interpolator; 7886 changed = true; 7887 } 7888 } 7889 } 7890 7891 static class AdapterDataObservable extends Observable<AdapterDataObserver> { hasObservers()7892 public boolean hasObservers() { 7893 return !mObservers.isEmpty(); 7894 } 7895 notifyChanged()7896 public void notifyChanged() { 7897 // since onChanged() is implemented by the app, it could do anything, including 7898 // removing itself from {@link mObservers} - and that could cause problems if 7899 // an iterator is used on the ArrayList {@link mObservers}. 7900 // to avoid such problems, just march thru the list in the reverse order. 7901 for (int i = mObservers.size() - 1; i >= 0; i--) { 7902 mObservers.get(i).onChanged(); 7903 } 7904 } 7905 notifyItemRangeChanged(int positionStart, int itemCount)7906 public void notifyItemRangeChanged(int positionStart, int itemCount) { 7907 // since onItemRangeChanged() is implemented by the app, it could do anything, including 7908 // removing itself from {@link mObservers} - and that could cause problems if 7909 // an iterator is used on the ArrayList {@link mObservers}. 7910 // to avoid such problems, just march thru the list in the reverse order. 7911 for (int i = mObservers.size() - 1; i >= 0; i--) { 7912 mObservers.get(i).onItemRangeChanged(positionStart, itemCount); 7913 } 7914 } 7915 notifyItemRangeInserted(int positionStart, int itemCount)7916 public void notifyItemRangeInserted(int positionStart, int itemCount) { 7917 // since onItemRangeInserted() is implemented by the app, it could do anything, 7918 // including removing itself from {@link mObservers} - and that could cause problems if 7919 // an iterator is used on the ArrayList {@link mObservers}. 7920 // to avoid such problems, just march thru the list in the reverse order. 7921 for (int i = mObservers.size() - 1; i >= 0; i--) { 7922 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 7923 } 7924 } 7925 notifyItemRangeRemoved(int positionStart, int itemCount)7926 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 7927 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 7928 // removing itself from {@link mObservers} - and that could cause problems if 7929 // an iterator is used on the ArrayList {@link mObservers}. 7930 // to avoid such problems, just march thru the list in the reverse order. 7931 for (int i = mObservers.size() - 1; i >= 0; i--) { 7932 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 7933 } 7934 } 7935 notifyItemMoved(int fromPosition, int toPosition)7936 public void notifyItemMoved(int fromPosition, int toPosition) { 7937 for (int i = mObservers.size() - 1; i >= 0; i--) { 7938 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); 7939 } 7940 } 7941 } 7942 7943 static class SavedState extends android.view.View.BaseSavedState { 7944 7945 Parcelable mLayoutState; 7946 7947 /** 7948 * called by CREATOR 7949 */ SavedState(Parcel in)7950 SavedState(Parcel in) { 7951 super(in); 7952 mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader()); 7953 } 7954 7955 /** 7956 * Called by onSaveInstanceState 7957 */ SavedState(Parcelable superState)7958 SavedState(Parcelable superState) { 7959 super(superState); 7960 } 7961 7962 @Override writeToParcel(Parcel dest, int flags)7963 public void writeToParcel(Parcel dest, int flags) { 7964 super.writeToParcel(dest, flags); 7965 dest.writeParcelable(mLayoutState, 0); 7966 } 7967 copyFrom(SavedState other)7968 private void copyFrom(SavedState other) { 7969 mLayoutState = other.mLayoutState; 7970 } 7971 7972 public static final Parcelable.Creator<SavedState> CREATOR 7973 = new Parcelable.Creator<SavedState>() { 7974 @Override 7975 public SavedState createFromParcel(Parcel in) { 7976 return new SavedState(in); 7977 } 7978 7979 @Override 7980 public SavedState[] newArray(int size) { 7981 return new SavedState[size]; 7982 } 7983 }; 7984 } 7985 /** 7986 * <p>Contains useful information about the current RecyclerView state like target scroll 7987 * position or view focus. State object can also keep arbitrary data, identified by resource 7988 * ids.</p> 7989 * <p>Often times, RecyclerView components will need to pass information between each other. 7990 * To provide a well defined data bus between components, RecyclerView passes the same State 7991 * object to component callbacks and these components can use it to exchange data.</p> 7992 * <p>If you implement custom components, you can use State's put/get/remove methods to pass 7993 * data between your components without needing to manage their lifecycles.</p> 7994 */ 7995 public static class State { 7996 7997 private int mTargetPosition = RecyclerView.NO_POSITION; 7998 ArrayMap<ViewHolder, ItemHolderInfo> mPreLayoutHolderMap = 7999 new ArrayMap<ViewHolder, ItemHolderInfo>(); 8000 ArrayMap<ViewHolder, ItemHolderInfo> mPostLayoutHolderMap = 8001 new ArrayMap<ViewHolder, ItemHolderInfo>(); 8002 // nullable 8003 ArrayMap<Long, ViewHolder> mOldChangedHolders = new ArrayMap<Long, ViewHolder>(); 8004 8005 private SparseArray<Object> mData; 8006 8007 /** 8008 * Number of items adapter has. 8009 */ 8010 int mItemCount = 0; 8011 8012 /** 8013 * Number of items adapter had in the previous layout. 8014 */ 8015 private int mPreviousLayoutItemCount = 0; 8016 8017 /** 8018 * Number of items that were NOT laid out but has been deleted from the adapter after the 8019 * previous layout. 8020 */ 8021 private int mDeletedInvisibleItemCountSincePreviousLayout = 0; 8022 8023 private boolean mStructureChanged = false; 8024 8025 private boolean mInPreLayout = false; 8026 8027 private boolean mRunSimpleAnimations = false; 8028 8029 private boolean mRunPredictiveAnimations = false; 8030 reset()8031 State reset() { 8032 mTargetPosition = RecyclerView.NO_POSITION; 8033 if (mData != null) { 8034 mData.clear(); 8035 } 8036 mItemCount = 0; 8037 mStructureChanged = false; 8038 return this; 8039 } 8040 isPreLayout()8041 public boolean isPreLayout() { 8042 return mInPreLayout; 8043 } 8044 8045 /** 8046 * Returns whether RecyclerView will run predictive animations in this layout pass 8047 * or not. 8048 * 8049 * @return true if RecyclerView is calculating predictive animations to be run at the end 8050 * of the layout pass. 8051 */ willRunPredictiveAnimations()8052 public boolean willRunPredictiveAnimations() { 8053 return mRunPredictiveAnimations; 8054 } 8055 8056 /** 8057 * Returns whether RecyclerView will run simple animations in this layout pass 8058 * or not. 8059 * 8060 * @return true if RecyclerView is calculating simple animations to be run at the end of 8061 * the layout pass. 8062 */ willRunSimpleAnimations()8063 public boolean willRunSimpleAnimations() { 8064 return mRunSimpleAnimations; 8065 } 8066 8067 /** 8068 * Removes the mapping from the specified id, if there was any. 8069 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to 8070 * preserve cross functionality and avoid conflicts. 8071 */ remove(int resourceId)8072 public void remove(int resourceId) { 8073 if (mData == null) { 8074 return; 8075 } 8076 mData.remove(resourceId); 8077 } 8078 8079 /** 8080 * Gets the Object mapped from the specified id, or <code>null</code> 8081 * if no such data exists. 8082 * 8083 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* 8084 * to 8085 * preserve cross functionality and avoid conflicts. 8086 */ get(int resourceId)8087 public <T> T get(int resourceId) { 8088 if (mData == null) { 8089 return null; 8090 } 8091 return (T) mData.get(resourceId); 8092 } 8093 8094 /** 8095 * Adds a mapping from the specified id to the specified value, replacing the previous 8096 * mapping from the specified key if there was one. 8097 * 8098 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to 8099 * preserve cross functionality and avoid conflicts. 8100 * @param data The data you want to associate with the resourceId. 8101 */ put(int resourceId, Object data)8102 public void put(int resourceId, Object data) { 8103 if (mData == null) { 8104 mData = new SparseArray<Object>(); 8105 } 8106 mData.put(resourceId, data); 8107 } 8108 8109 /** 8110 * If scroll is triggered to make a certain item visible, this value will return the 8111 * adapter index of that item. 8112 * @return Adapter index of the target item or 8113 * {@link RecyclerView#NO_POSITION} if there is no target 8114 * position. 8115 */ getTargetScrollPosition()8116 public int getTargetScrollPosition() { 8117 return mTargetPosition; 8118 } 8119 8120 /** 8121 * Returns if current scroll has a target position. 8122 * @return true if scroll is being triggered to make a certain position visible 8123 * @see #getTargetScrollPosition() 8124 */ hasTargetScrollPosition()8125 public boolean hasTargetScrollPosition() { 8126 return mTargetPosition != RecyclerView.NO_POSITION; 8127 } 8128 8129 /** 8130 * @return true if the structure of the data set has changed since the last call to 8131 * onLayoutChildren, false otherwise 8132 */ didStructureChange()8133 public boolean didStructureChange() { 8134 return mStructureChanged; 8135 } 8136 8137 /** 8138 * Returns the total number of items that can be laid out. Note that this number is not 8139 * necessarily equal to the number of items in the adapter, so you should always use this 8140 * number for your position calculations and never access the adapter directly. 8141 * <p> 8142 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter 8143 * data changes on existing Views. These calculations are used to decide which animations 8144 * should be run. 8145 * <p> 8146 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to 8147 * present the correct state to LayoutManager in pre-layout pass. 8148 * <p> 8149 * For example, a newly added item is not included in pre-layout item count because 8150 * pre-layout reflects the contents of the adapter before the item is added. Behind the 8151 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that 8152 * LayoutManager does not know about the new item's existence in pre-layout. The item will 8153 * be available in second layout pass and will be included in the item count. Similar 8154 * adjustments are made for moved and removed items as well. 8155 * <p> 8156 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. 8157 * 8158 * @return The number of items currently available 8159 * @see LayoutManager#getItemCount() 8160 */ getItemCount()8161 public int getItemCount() { 8162 return mInPreLayout ? 8163 (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) : 8164 mItemCount; 8165 } 8166 onViewRecycled(ViewHolder holder)8167 public void onViewRecycled(ViewHolder holder) { 8168 mPreLayoutHolderMap.remove(holder); 8169 mPostLayoutHolderMap.remove(holder); 8170 if (mOldChangedHolders != null) { 8171 removeFrom(mOldChangedHolders, holder); 8172 } 8173 // holder cannot be in new list. 8174 } 8175 onViewIgnored(ViewHolder holder)8176 public void onViewIgnored(ViewHolder holder) { 8177 onViewRecycled(holder); 8178 } 8179 removeFrom(ArrayMap<Long, ViewHolder> holderMap, ViewHolder holder)8180 private void removeFrom(ArrayMap<Long, ViewHolder> holderMap, ViewHolder holder) { 8181 for (int i = holderMap.size() - 1; i >= 0; i --) { 8182 if (holder == holderMap.valueAt(i)) { 8183 holderMap.removeAt(i); 8184 return; 8185 } 8186 } 8187 } 8188 8189 @Override toString()8190 public String toString() { 8191 return "State{" + 8192 "mTargetPosition=" + mTargetPosition + 8193 ", mPreLayoutHolderMap=" + mPreLayoutHolderMap + 8194 ", mPostLayoutHolderMap=" + mPostLayoutHolderMap + 8195 ", mData=" + mData + 8196 ", mItemCount=" + mItemCount + 8197 ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount + 8198 ", mDeletedInvisibleItemCountSincePreviousLayout=" 8199 + mDeletedInvisibleItemCountSincePreviousLayout + 8200 ", mStructureChanged=" + mStructureChanged + 8201 ", mInPreLayout=" + mInPreLayout + 8202 ", mRunSimpleAnimations=" + mRunSimpleAnimations + 8203 ", mRunPredictiveAnimations=" + mRunPredictiveAnimations + 8204 '}'; 8205 } 8206 } 8207 8208 /** 8209 * Internal listener that manages items after animations finish. This is how items are 8210 * retained (not recycled) during animations, but allowed to be recycled afterwards. 8211 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() 8212 * method on the animator's listener when it is done animating any item. 8213 */ 8214 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { 8215 8216 @Override onRemoveFinished(ViewHolder item)8217 public void onRemoveFinished(ViewHolder item) { 8218 item.setIsRecyclable(true); 8219 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { 8220 removeDetachedView(item.itemView, false); 8221 } 8222 } 8223 8224 @Override onAddFinished(ViewHolder item)8225 public void onAddFinished(ViewHolder item) { 8226 item.setIsRecyclable(true); 8227 if (!item.shouldBeKeptAsChild()) { 8228 removeAnimatingView(item.itemView); 8229 } 8230 } 8231 8232 @Override onMoveFinished(ViewHolder item)8233 public void onMoveFinished(ViewHolder item) { 8234 item.setIsRecyclable(true); 8235 if (!item.shouldBeKeptAsChild()) { 8236 removeAnimatingView(item.itemView); 8237 } 8238 } 8239 8240 @Override onChangeFinished(ViewHolder item)8241 public void onChangeFinished(ViewHolder item) { 8242 item.setIsRecyclable(true); 8243 /** 8244 * We check both shadowed and shadowing because a ViewHolder may get both roles at the 8245 * same time. 8246 * 8247 * Assume this flow: 8248 * item X is represented by VH_1. Then itemX changes, so we create VH_2 . 8249 * RV sets the following and calls item animator: 8250 * VH_1.shadowed = VH_2; 8251 * VH_1.mChanged = true; 8252 * VH_2.shadowing =VH_1; 8253 * 8254 * Then, before the first change finishes, item changes again so we create VH_3. 8255 * RV sets the following and calls item animator: 8256 * VH_2.shadowed = VH_3 8257 * VH_2.mChanged = true 8258 * VH_3.shadowing = VH_2 8259 * 8260 * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has 8261 * both shadowing and shadowed fields set. Shadowing information is obsolete now 8262 * because the first animation where VH_2 is newViewHolder is not valid anymore. 8263 * We ended up in this case because VH_2 played both roles. On the other hand, 8264 * we DO NOT want to clear its changed flag. 8265 * 8266 * If second change was simply reverting first change, we would find VH_1 in 8267 * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before 8268 * re-using 8269 */ 8270 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh 8271 item.mShadowedHolder = null; 8272 item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags); 8273 } 8274 // always null this because an OldViewHolder can never become NewViewHolder w/o being 8275 // recycled. 8276 item.mShadowingHolder = null; 8277 if (!item.shouldBeKeptAsChild()) { 8278 removeAnimatingView(item.itemView); 8279 } 8280 } 8281 }; 8282 8283 /** 8284 * This class defines the animations that take place on items as changes are made 8285 * to the adapter. 8286 * 8287 * Subclasses of ItemAnimator can be used to implement custom animations for actions on 8288 * ViewHolder items. The RecyclerView will manage retaining these items while they 8289 * are being animated, but implementors must call the appropriate "Starting" 8290 * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)}, 8291 * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or 8292 * {@link #dispatchAddStarting(ViewHolder)}) 8293 * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)}, 8294 * {@link #dispatchMoveFinished(ViewHolder)}, 8295 * {@link #dispatchChangeFinished(ViewHolder, boolean)}, 8296 * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is 8297 * being started and ended. 8298 * 8299 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}</p> 8300 * 8301 * @see #setItemAnimator(ItemAnimator) 8302 */ 8303 public static abstract class ItemAnimator { 8304 8305 private ItemAnimatorListener mListener = null; 8306 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = 8307 new ArrayList<ItemAnimatorFinishedListener>(); 8308 8309 private long mAddDuration = 120; 8310 private long mRemoveDuration = 120; 8311 private long mMoveDuration = 250; 8312 private long mChangeDuration = 250; 8313 8314 private boolean mSupportsChangeAnimations = true; 8315 8316 /** 8317 * Gets the current duration for which all move animations will run. 8318 * 8319 * @return The current move duration 8320 */ getMoveDuration()8321 public long getMoveDuration() { 8322 return mMoveDuration; 8323 } 8324 8325 /** 8326 * Sets the duration for which all move animations will run. 8327 * 8328 * @param moveDuration The move duration 8329 */ setMoveDuration(long moveDuration)8330 public void setMoveDuration(long moveDuration) { 8331 mMoveDuration = moveDuration; 8332 } 8333 8334 /** 8335 * Gets the current duration for which all add animations will run. 8336 * 8337 * @return The current add duration 8338 */ getAddDuration()8339 public long getAddDuration() { 8340 return mAddDuration; 8341 } 8342 8343 /** 8344 * Sets the duration for which all add animations will run. 8345 * 8346 * @param addDuration The add duration 8347 */ setAddDuration(long addDuration)8348 public void setAddDuration(long addDuration) { 8349 mAddDuration = addDuration; 8350 } 8351 8352 /** 8353 * Gets the current duration for which all remove animations will run. 8354 * 8355 * @return The current remove duration 8356 */ getRemoveDuration()8357 public long getRemoveDuration() { 8358 return mRemoveDuration; 8359 } 8360 8361 /** 8362 * Sets the duration for which all remove animations will run. 8363 * 8364 * @param removeDuration The remove duration 8365 */ setRemoveDuration(long removeDuration)8366 public void setRemoveDuration(long removeDuration) { 8367 mRemoveDuration = removeDuration; 8368 } 8369 8370 /** 8371 * Gets the current duration for which all change animations will run. 8372 * 8373 * @return The current change duration 8374 */ getChangeDuration()8375 public long getChangeDuration() { 8376 return mChangeDuration; 8377 } 8378 8379 /** 8380 * Sets the duration for which all change animations will run. 8381 * 8382 * @param changeDuration The change duration 8383 */ setChangeDuration(long changeDuration)8384 public void setChangeDuration(long changeDuration) { 8385 mChangeDuration = changeDuration; 8386 } 8387 8388 /** 8389 * Returns whether this ItemAnimator supports animations of change events. 8390 * 8391 * @return true if change animations are supported, false otherwise 8392 */ getSupportsChangeAnimations()8393 public boolean getSupportsChangeAnimations() { 8394 return mSupportsChangeAnimations; 8395 } 8396 8397 /** 8398 * Sets whether this ItemAnimator supports animations of item change events. 8399 * If you set this property to false, actions on the data set which change the 8400 * contents of items will not be animated. What those animations are is left 8401 * up to the discretion of the ItemAnimator subclass, in its 8402 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation. 8403 * The value of this property is true by default. 8404 * 8405 * @see Adapter#notifyItemChanged(int) 8406 * @see Adapter#notifyItemRangeChanged(int, int) 8407 * 8408 * @param supportsChangeAnimations true if change animations are supported by 8409 * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator 8410 * will not receive a call to 8411 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur. 8412 */ setSupportsChangeAnimations(boolean supportsChangeAnimations)8413 public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { 8414 mSupportsChangeAnimations = supportsChangeAnimations; 8415 } 8416 8417 /** 8418 * Internal only: 8419 * Sets the listener that must be called when the animator is finished 8420 * animating the item (or immediately if no animation happens). This is set 8421 * internally and is not intended to be set by external code. 8422 * 8423 * @param listener The listener that must be called. 8424 */ setListener(ItemAnimatorListener listener)8425 void setListener(ItemAnimatorListener listener) { 8426 mListener = listener; 8427 } 8428 8429 /** 8430 * Called when there are pending animations waiting to be started. This state 8431 * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()}, 8432 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and 8433 * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the 8434 * RecyclerView that the ItemAnimator wants to be called later to start the 8435 * associated animations. runPendingAnimations() will be scheduled to be run 8436 * on the next frame. 8437 */ runPendingAnimations()8438 abstract public void runPendingAnimations(); 8439 8440 /** 8441 * Called when an item is removed from the RecyclerView. Implementors can choose 8442 * whether and how to animate that change, but must always call 8443 * {@link #dispatchRemoveFinished(ViewHolder)} when done, either 8444 * immediately (if no animation will occur) or after the animation actually finishes. 8445 * The return value indicates whether an animation has been set up and whether the 8446 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8447 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8448 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8449 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8450 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8451 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8452 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8453 * 8454 * <p>This method may also be called for disappearing items which continue to exist in the 8455 * RecyclerView, but for which the system does not have enough information to animate 8456 * them out of view. In that case, the default animation for removing items is run 8457 * on those items as well.</p> 8458 * 8459 * @param holder The item that is being removed. 8460 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8461 * false otherwise. 8462 */ animateRemove(ViewHolder holder)8463 abstract public boolean animateRemove(ViewHolder holder); 8464 8465 /** 8466 * Called when an item is added to the RecyclerView. Implementors can choose 8467 * whether and how to animate that change, but must always call 8468 * {@link #dispatchAddFinished(ViewHolder)} when done, either 8469 * immediately (if no animation will occur) or after the animation actually finishes. 8470 * The return value indicates whether an animation has been set up and whether the 8471 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8472 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8473 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8474 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8475 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8476 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8477 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8478 * 8479 * <p>This method may also be called for appearing items which were already in the 8480 * RecyclerView, but for which the system does not have enough information to animate 8481 * them into view. In that case, the default animation for adding items is run 8482 * on those items as well.</p> 8483 * 8484 * @param holder The item that is being added. 8485 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8486 * false otherwise. 8487 */ animateAdd(ViewHolder holder)8488 abstract public boolean animateAdd(ViewHolder holder); 8489 8490 /** 8491 * Called when an item is moved in the RecyclerView. Implementors can choose 8492 * whether and how to animate that change, but must always call 8493 * {@link #dispatchMoveFinished(ViewHolder)} when done, either 8494 * immediately (if no animation will occur) or after the animation actually finishes. 8495 * The return value indicates whether an animation has been set up and whether the 8496 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8497 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8498 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8499 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8500 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8501 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8502 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8503 * 8504 * @param holder The item that is being moved. 8505 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8506 * false otherwise. 8507 */ animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY)8508 abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY, 8509 int toX, int toY); 8510 8511 /** 8512 * Called when an item is changed in the RecyclerView, as indicated by a call to 8513 * {@link Adapter#notifyItemChanged(int)} or 8514 * {@link Adapter#notifyItemRangeChanged(int, int)}. 8515 * <p> 8516 * Implementers can choose whether and how to animate changes, but must always call 8517 * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder, 8518 * either immediately (if no animation will occur) or after the animation actually finishes. 8519 * The return value indicates whether an animation has been set up and whether the 8520 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8521 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8522 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8523 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8524 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8525 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8526 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8527 * 8528 * @param oldHolder The original item that changed. 8529 * @param newHolder The new item that was created with the changed content. Might be null 8530 * @param fromLeft Left of the old view holder 8531 * @param fromTop Top of the old view holder 8532 * @param toLeft Left of the new view holder 8533 * @param toTop Top of the new view holder 8534 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8535 * false otherwise. 8536 */ animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)8537 abstract public boolean animateChange(ViewHolder oldHolder, 8538 ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); 8539 8540 8541 /** 8542 * Method to be called by subclasses when a remove animation is done. 8543 * 8544 * @param item The item which has been removed 8545 */ dispatchRemoveFinished(ViewHolder item)8546 public final void dispatchRemoveFinished(ViewHolder item) { 8547 onRemoveFinished(item); 8548 if (mListener != null) { 8549 mListener.onRemoveFinished(item); 8550 } 8551 } 8552 8553 /** 8554 * Method to be called by subclasses when a move animation is done. 8555 * 8556 * @param item The item which has been moved 8557 */ dispatchMoveFinished(ViewHolder item)8558 public final void dispatchMoveFinished(ViewHolder item) { 8559 onMoveFinished(item); 8560 if (mListener != null) { 8561 mListener.onMoveFinished(item); 8562 } 8563 } 8564 8565 /** 8566 * Method to be called by subclasses when an add animation is done. 8567 * 8568 * @param item The item which has been added 8569 */ dispatchAddFinished(ViewHolder item)8570 public final void dispatchAddFinished(ViewHolder item) { 8571 onAddFinished(item); 8572 if (mListener != null) { 8573 mListener.onAddFinished(item); 8574 } 8575 } 8576 8577 /** 8578 * Method to be called by subclasses when a change animation is done. 8579 * 8580 * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int) 8581 * @param item The item which has been changed (this method must be called for 8582 * each non-null ViewHolder passed into 8583 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). 8584 * @param oldItem true if this is the old item that was changed, false if 8585 * it is the new item that replaced the old item. 8586 */ dispatchChangeFinished(ViewHolder item, boolean oldItem)8587 public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { 8588 onChangeFinished(item, oldItem); 8589 if (mListener != null) { 8590 mListener.onChangeFinished(item); 8591 } 8592 } 8593 8594 /** 8595 * Method to be called by subclasses when a remove animation is being started. 8596 * 8597 * @param item The item being removed 8598 */ dispatchRemoveStarting(ViewHolder item)8599 public final void dispatchRemoveStarting(ViewHolder item) { 8600 onRemoveStarting(item); 8601 } 8602 8603 /** 8604 * Method to be called by subclasses when a move animation is being started. 8605 * 8606 * @param item The item being moved 8607 */ dispatchMoveStarting(ViewHolder item)8608 public final void dispatchMoveStarting(ViewHolder item) { 8609 onMoveStarting(item); 8610 } 8611 8612 /** 8613 * Method to be called by subclasses when an add animation is being started. 8614 * 8615 * @param item The item being added 8616 */ dispatchAddStarting(ViewHolder item)8617 public final void dispatchAddStarting(ViewHolder item) { 8618 onAddStarting(item); 8619 } 8620 8621 /** 8622 * Method to be called by subclasses when a change animation is being started. 8623 * 8624 * @param item The item which has been changed (this method must be called for 8625 * each non-null ViewHolder passed into 8626 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). 8627 * @param oldItem true if this is the old item that was changed, false if 8628 * it is the new item that replaced the old item. 8629 */ dispatchChangeStarting(ViewHolder item, boolean oldItem)8630 public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) { 8631 onChangeStarting(item, oldItem); 8632 } 8633 8634 /** 8635 * Method called when an animation on a view should be ended immediately. 8636 * This could happen when other events, like scrolling, occur, so that 8637 * animating views can be quickly put into their proper end locations. 8638 * Implementations should ensure that any animations running on the item 8639 * are canceled and affected properties are set to their end values. 8640 * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} 8641 * should be called since the animations are effectively done when this 8642 * method is called. 8643 * 8644 * @param item The item for which an animation should be stopped. 8645 */ endAnimation(ViewHolder item)8646 abstract public void endAnimation(ViewHolder item); 8647 8648 /** 8649 * Method called when all item animations should be ended immediately. 8650 * This could happen when other events, like scrolling, occur, so that 8651 * animating views can be quickly put into their proper end locations. 8652 * Implementations should ensure that any animations running on any items 8653 * are canceled and affected properties are set to their end values. 8654 * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} 8655 * should be called since the animations are effectively done when this 8656 * method is called. 8657 */ endAnimations()8658 abstract public void endAnimations(); 8659 8660 /** 8661 * Method which returns whether there are any item animations currently running. 8662 * This method can be used to determine whether to delay other actions until 8663 * animations end. 8664 * 8665 * @return true if there are any item animations currently running, false otherwise. 8666 */ isRunning()8667 abstract public boolean isRunning(); 8668 8669 /** 8670 * Like {@link #isRunning()}, this method returns whether there are any item 8671 * animations currently running. Addtionally, the listener passed in will be called 8672 * when there are no item animations running, either immediately (before the method 8673 * returns) if no animations are currently running, or when the currently running 8674 * animations are {@link #dispatchAnimationsFinished() finished}. 8675 * 8676 * <p>Note that the listener is transient - it is either called immediately and not 8677 * stored at all, or stored only until it is called when running animations 8678 * are finished sometime later.</p> 8679 * 8680 * @param listener A listener to be called immediately if no animations are running 8681 * or later when currently-running animations have finished. A null listener is 8682 * equivalent to calling {@link #isRunning()}. 8683 * @return true if there are any item animations currently running, false otherwise. 8684 */ isRunning(ItemAnimatorFinishedListener listener)8685 public final boolean isRunning(ItemAnimatorFinishedListener listener) { 8686 boolean running = isRunning(); 8687 if (listener != null) { 8688 if (!running) { 8689 listener.onAnimationsFinished(); 8690 } else { 8691 mFinishedListeners.add(listener); 8692 } 8693 } 8694 return running; 8695 } 8696 8697 /** 8698 * The interface to be implemented by listeners to animation events from this 8699 * ItemAnimator. This is used internally and is not intended for developers to 8700 * create directly. 8701 */ 8702 interface ItemAnimatorListener { onRemoveFinished(ViewHolder item)8703 void onRemoveFinished(ViewHolder item); onAddFinished(ViewHolder item)8704 void onAddFinished(ViewHolder item); onMoveFinished(ViewHolder item)8705 void onMoveFinished(ViewHolder item); onChangeFinished(ViewHolder item)8706 void onChangeFinished(ViewHolder item); 8707 } 8708 8709 /** 8710 * This method should be called by ItemAnimator implementations to notify 8711 * any listeners that all pending and active item animations are finished. 8712 */ dispatchAnimationsFinished()8713 public final void dispatchAnimationsFinished() { 8714 final int count = mFinishedListeners.size(); 8715 for (int i = 0; i < count; ++i) { 8716 mFinishedListeners.get(i).onAnimationsFinished(); 8717 } 8718 mFinishedListeners.clear(); 8719 } 8720 8721 /** 8722 * This interface is used to inform listeners when all pending or running animations 8723 * in an ItemAnimator are finished. This can be used, for example, to delay an action 8724 * in a data set until currently-running animations are complete. 8725 * 8726 * @see #isRunning(ItemAnimatorFinishedListener) 8727 */ 8728 public interface ItemAnimatorFinishedListener { onAnimationsFinished()8729 void onAnimationsFinished(); 8730 } 8731 8732 /** 8733 * Called when a remove animation is being started on the given ViewHolder. 8734 * The default implementation does nothing. Subclasses may wish to override 8735 * this method to handle any ViewHolder-specific operations linked to animation 8736 * lifecycles. 8737 * 8738 * @param item The ViewHolder being animated. 8739 */ onRemoveStarting(ViewHolder item)8740 public void onRemoveStarting(ViewHolder item) {} 8741 8742 /** 8743 * Called when a remove animation has ended on the given ViewHolder. 8744 * The default implementation does nothing. Subclasses may wish to override 8745 * this method to handle any ViewHolder-specific operations linked to animation 8746 * lifecycles. 8747 * 8748 * @param item The ViewHolder being animated. 8749 */ onRemoveFinished(ViewHolder item)8750 public void onRemoveFinished(ViewHolder item) {} 8751 8752 /** 8753 * Called when an add animation is being started on the given ViewHolder. 8754 * The default implementation does nothing. Subclasses may wish to override 8755 * this method to handle any ViewHolder-specific operations linked to animation 8756 * lifecycles. 8757 * 8758 * @param item The ViewHolder being animated. 8759 */ onAddStarting(ViewHolder item)8760 public void onAddStarting(ViewHolder item) {} 8761 8762 /** 8763 * Called when an add animation has ended on the given ViewHolder. 8764 * The default implementation does nothing. Subclasses may wish to override 8765 * this method to handle any ViewHolder-specific operations linked to animation 8766 * lifecycles. 8767 * 8768 * @param item The ViewHolder being animated. 8769 */ onAddFinished(ViewHolder item)8770 public void onAddFinished(ViewHolder item) {} 8771 8772 /** 8773 * Called when a move animation is being started on the given ViewHolder. 8774 * The default implementation does nothing. Subclasses may wish to override 8775 * this method to handle any ViewHolder-specific operations linked to animation 8776 * lifecycles. 8777 * 8778 * @param item The ViewHolder being animated. 8779 */ onMoveStarting(ViewHolder item)8780 public void onMoveStarting(ViewHolder item) {} 8781 8782 /** 8783 * Called when a move animation has ended on the given ViewHolder. 8784 * The default implementation does nothing. Subclasses may wish to override 8785 * this method to handle any ViewHolder-specific operations linked to animation 8786 * lifecycles. 8787 * 8788 * @param item The ViewHolder being animated. 8789 */ onMoveFinished(ViewHolder item)8790 public void onMoveFinished(ViewHolder item) {} 8791 8792 /** 8793 * Called when a change animation is being started on the given ViewHolder. 8794 * The default implementation does nothing. Subclasses may wish to override 8795 * this method to handle any ViewHolder-specific operations linked to animation 8796 * lifecycles. 8797 * 8798 * @param item The ViewHolder being animated. 8799 * @param oldItem true if this is the old item that was changed, false if 8800 * it is the new item that replaced the old item. 8801 */ onChangeStarting(ViewHolder item, boolean oldItem)8802 public void onChangeStarting(ViewHolder item, boolean oldItem) {} 8803 8804 /** 8805 * Called when a change animation has ended on the given ViewHolder. 8806 * The default implementation does nothing. Subclasses may wish to override 8807 * this method to handle any ViewHolder-specific operations linked to animation 8808 * lifecycles. 8809 * 8810 * @param item The ViewHolder being animated. 8811 * @param oldItem true if this is the old item that was changed, false if 8812 * it is the new item that replaced the old item. 8813 */ onChangeFinished(ViewHolder item, boolean oldItem)8814 public void onChangeFinished(ViewHolder item, boolean oldItem) {} 8815 8816 } 8817 8818 /** 8819 * Internal data structure that holds information about an item's bounds. 8820 * This information is used in calculating item animations. 8821 */ 8822 private static class ItemHolderInfo { 8823 ViewHolder holder; 8824 int left, top, right, bottom; 8825 ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom)8826 ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) { 8827 this.holder = holder; 8828 this.left = left; 8829 this.top = top; 8830 this.right = right; 8831 this.bottom = bottom; 8832 } 8833 } 8834 } 8835