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