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