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