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