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