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