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