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