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