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