RecyclerView.java revision 54693c0ed6f4bfa968f86158b2062c5688413b6c
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.Bundle; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.support.annotation.CallSuper; 30import android.support.annotation.Nullable; 31import android.support.v4.util.ArrayMap; 32import android.support.v4.view.InputDeviceCompat; 33import android.support.v4.view.MotionEventCompat; 34import android.support.v4.view.ScrollingView; 35import android.support.v4.view.VelocityTrackerCompat; 36import android.support.v4.view.ViewCompat; 37import android.support.v4.view.ViewConfigurationCompat; 38import android.support.v4.view.accessibility.AccessibilityEventCompat; 39import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 40import android.support.v4.view.accessibility.AccessibilityRecordCompat; 41import android.support.v4.widget.EdgeEffectCompat; 42import android.support.v4.widget.ScrollerCompat; 43import static android.support.v7.widget.AdapterHelper.UpdateOp; 44import static android.support.v7.widget.AdapterHelper.Callback; 45 46import android.util.AttributeSet; 47import android.util.Log; 48import android.util.SparseArray; 49import android.util.SparseIntArray; 50import android.util.TypedValue; 51import android.view.FocusFinder; 52import android.view.MotionEvent; 53import android.view.VelocityTracker; 54import android.view.View; 55import android.view.ViewConfiguration; 56import android.view.ViewGroup; 57import android.view.ViewParent; 58import android.view.accessibility.AccessibilityEvent; 59import android.view.accessibility.AccessibilityManager; 60import android.view.animation.Interpolator; 61 62import java.util.ArrayList; 63import java.util.Collections; 64import java.util.List; 65 66/** 67 * A flexible view for providing a limited window into a large data set. 68 * 69 * <h3>Glossary of terms:</h3> 70 * 71 * <ul> 72 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 73 * that represent items in a data set.</li> 74 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 75 * <li><em>Index:</em> The index of an attached child view as used in a call to 76 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 77 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 78 * to a <em>position</em> within the adapter.</li> 79 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 80 * position may be placed in a cache for later reuse to display the same type of data again 81 * later. This can drastically improve performance by skipping initial layout inflation 82 * or construction.</li> 83 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 84 * state during layout. Scrap views may be reused without becoming fully detached 85 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 86 * by the adapter if the view was considered <em>dirty</em>.</li> 87 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 88 * being displayed.</li> 89 * </ul> 90 * 91 * <h4>Positions in RecyclerView:</h4> 92 * <p> 93 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and 94 * {@link LayoutManager} to be able to detect data set changes in batches during a layout 95 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. 96 * It also helps with performance because all view bindings happen at the same time and unnecessary 97 * bindings are avoided. 98 * <p> 99 * For this reason, there are two types of <code>position</code> related methods in RecyclerView: 100 * <ul> 101 * <li>layout position: Position of an item in the latest layout calculation. This is the 102 * position from the LayoutManager's perspective.</li> 103 * <li>adapter position: Position of an item in the adapter. This is the position from 104 * the Adapter's perspective.</li> 105 * </ul> 106 * <p> 107 * These two positions are the same except the time between dispatching <code>adapter.notify* 108 * </code> events and calculating the updated layout. 109 * <p> 110 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest 111 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, 112 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the 113 * last layout calculation. You can rely on these positions to be consistent with what user is 114 * currently seeing on the screen. For example, if you have a list of items on the screen and user 115 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user 116 * is seeing. 117 * <p> 118 * The other set of position related methods are in the form of 119 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()}, 120 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to 121 * work with up-to-date adapter positions even if they may not have been reflected to layout yet. 122 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use 123 * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate 124 * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has 125 * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or 126 * <code>null</code> results from these methods. 127 * <p> 128 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when 129 * writing an {@link Adapter}, you probably want to use adapter positions. 130 */ 131public class RecyclerView extends ViewGroup implements ScrollingView { 132 private static final String TAG = "RecyclerView"; 133 134 private static final boolean DEBUG = false; 135 136 /** 137 * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if 138 * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by 139 * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler 140 * recursively traverses itemView and invalidates display list for each ViewGroup that matches 141 * this criteria. 142 */ 143 private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 144 || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; 145 146 private static final boolean DISPATCH_TEMP_DETACH = false; 147 public static final int HORIZONTAL = 0; 148 public static final int VERTICAL = 1; 149 150 public static final int NO_POSITION = -1; 151 public static final long NO_ID = -1; 152 public static final int INVALID_TYPE = -1; 153 154 /** 155 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 156 * that the RecyclerView should use the standard touch slop for smooth, 157 * continuous scrolling. 158 */ 159 public static final int TOUCH_SLOP_DEFAULT = 0; 160 161 /** 162 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 163 * that the RecyclerView should use the standard touch slop for scrolling 164 * widgets that snap to a page or other coarse-grained barrier. 165 */ 166 public static final int TOUCH_SLOP_PAGING = 1; 167 168 private static final int MAX_SCROLL_DURATION = 2000; 169 170 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 171 172 final Recycler mRecycler = new Recycler(); 173 174 private SavedState mPendingSavedState; 175 176 AdapterHelper mAdapterHelper; 177 178 ChildHelper mChildHelper; 179 180 // we use this like a set 181 final List<View> mDisappearingViewsInLayoutPass = new ArrayList<View>(); 182 183 /** 184 * Prior to L, there is no way to query this variable which is why we override the setter and 185 * track it here. 186 */ 187 private boolean mClipToPadding; 188 189 /** 190 * Note: this Runnable is only ever posted if: 191 * 1) We've been through first layout 192 * 2) We know we have a fixed size (mHasFixedSize) 193 * 3) We're attached 194 */ 195 private final Runnable mUpdateChildViewsRunnable = new Runnable() { 196 public void run() { 197 if (!mFirstLayoutComplete) { 198 // a layout request will happen, we should not do layout here. 199 return; 200 } 201 if (mDataSetHasChangedAfterLayout) { 202 dispatchLayout(); 203 } else if (mAdapterHelper.hasPendingUpdates()) { 204 eatRequestLayout(); 205 mAdapterHelper.preProcess(); 206 if (!mLayoutRequestEaten) { 207 // We run this after pre-processing is complete so that ViewHolders have their 208 // final adapter positions. No need to run it if a layout is already requested. 209 rebindUpdatedViewHolders(); 210 } 211 resumeRequestLayout(true); 212 } 213 } 214 }; 215 216 private final Rect mTempRect = new Rect(); 217 private Adapter mAdapter; 218 private LayoutManager mLayout; 219 private RecyclerListener mRecyclerListener; 220 private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>(); 221 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 222 new ArrayList<OnItemTouchListener>(); 223 private OnItemTouchListener mActiveOnItemTouchListener; 224 private boolean mIsAttached; 225 private boolean mHasFixedSize; 226 private boolean mFirstLayoutComplete; 227 private boolean mEatRequestLayout; 228 private boolean mLayoutRequestEaten; 229 private boolean mAdapterUpdateDuringMeasure; 230 private final boolean mPostUpdatesOnAnimation; 231 private final AccessibilityManager mAccessibilityManager; 232 233 /** 234 * Set to true when an adapter data set changed notification is received. 235 * In that case, we cannot run any animations since we don't know what happened. 236 */ 237 private boolean mDataSetHasChangedAfterLayout = false; 238 239 /** 240 * This variable is set to true during a dispatchLayout and/or scroll. 241 * Some methods should not be called during these periods (e.g. adapter data change). 242 * Doing so will create hard to find bugs so we better check it and throw an exception. 243 * 244 * @see #assertInLayoutOrScroll(String) 245 * @see #assertNotInLayoutOrScroll(String) 246 */ 247 private boolean mRunningLayoutOrScroll = false; 248 249 private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 250 251 ItemAnimator mItemAnimator = new DefaultItemAnimator(); 252 253 private static final int INVALID_POINTER = -1; 254 255 /** 256 * The RecyclerView is not currently scrolling. 257 * @see #getScrollState() 258 */ 259 public static final int SCROLL_STATE_IDLE = 0; 260 261 /** 262 * The RecyclerView is currently being dragged by outside input such as user touch input. 263 * @see #getScrollState() 264 */ 265 public static final int SCROLL_STATE_DRAGGING = 1; 266 267 /** 268 * The RecyclerView is currently animating to a final position while not under 269 * outside control. 270 * @see #getScrollState() 271 */ 272 public static final int SCROLL_STATE_SETTLING = 2; 273 274 // Touch/scrolling handling 275 276 private int mScrollState = SCROLL_STATE_IDLE; 277 private int mScrollPointerId = INVALID_POINTER; 278 private VelocityTracker mVelocityTracker; 279 private int mInitialTouchX; 280 private int mInitialTouchY; 281 private int mLastTouchX; 282 private int mLastTouchY; 283 private int mTouchSlop; 284 private final int mMinFlingVelocity; 285 private final int mMaxFlingVelocity; 286 // This value is used when handling generic motion events. 287 private float mScrollFactor = Float.MIN_VALUE; 288 289 private final ViewFlinger mViewFlinger = new ViewFlinger(); 290 291 final State mState = new State(); 292 293 private OnScrollListener mScrollListener; 294 private List<OnScrollListener> mScrollListeners; 295 296 // For use in item animations 297 boolean mItemsAddedOrRemoved = false; 298 boolean mItemsChanged = false; 299 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = 300 new ItemAnimatorRestoreListener(); 301 private boolean mPostedAnimatorRunner = false; 302 private RecyclerViewAccessibilityDelegate mAccessibilityDelegate; 303 304 // simple array to keep min and max child position during a layout calculation 305 // preserved not to create a new one in each layout pass 306 private final int[] mMinMaxLayoutPositions = new int[2]; 307 308 private Runnable mItemAnimatorRunner = new Runnable() { 309 @Override 310 public void run() { 311 if (mItemAnimator != null) { 312 mItemAnimator.runPendingAnimations(); 313 } 314 mPostedAnimatorRunner = false; 315 } 316 }; 317 318 private static final Interpolator sQuinticInterpolator = new Interpolator() { 319 public float getInterpolation(float t) { 320 t -= 1.0f; 321 return t * t * t * t * t + 1.0f; 322 } 323 }; 324 325 public RecyclerView(Context context) { 326 this(context, null); 327 } 328 329 public RecyclerView(Context context, @Nullable AttributeSet attrs) { 330 this(context, attrs, 0); 331 } 332 333 public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 334 super(context, attrs, defStyle); 335 setFocusableInTouchMode(true); 336 final int version = Build.VERSION.SDK_INT; 337 mPostUpdatesOnAnimation = version >= 16; 338 339 final ViewConfiguration vc = ViewConfiguration.get(context); 340 mTouchSlop = vc.getScaledTouchSlop(); 341 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 342 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 343 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 344 345 mItemAnimator.setListener(mItemAnimatorListener); 346 initAdapterManager(); 347 initChildrenHelper(); 348 // If not explicitly specified this view is important for accessibility. 349 if (ViewCompat.getImportantForAccessibility(this) 350 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 351 ViewCompat.setImportantForAccessibility(this, 352 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 353 } 354 mAccessibilityManager = (AccessibilityManager) getContext() 355 .getSystemService(Context.ACCESSIBILITY_SERVICE); 356 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 357 } 358 359 /** 360 * Returns the accessibility delegate compatibility implementation used by the RecyclerView. 361 * @return An instance of AccessibilityDelegateCompat used by RecyclerView 362 */ 363 public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { 364 return mAccessibilityDelegate; 365 } 366 367 /** 368 * Sets the accessibility delegate compatibility implementation used by RecyclerView. 369 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. 370 */ 371 public void setAccessibilityDelegateCompat( 372 RecyclerViewAccessibilityDelegate accessibilityDelegate) { 373 mAccessibilityDelegate = accessibilityDelegate; 374 ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); 375 } 376 377 private void initChildrenHelper() { 378 mChildHelper = new ChildHelper(new ChildHelper.Callback() { 379 @Override 380 public int getChildCount() { 381 return RecyclerView.this.getChildCount(); 382 } 383 384 @Override 385 public void addView(View child, int index) { 386 RecyclerView.this.addView(child, index); 387 dispatchChildAttached(child); 388 } 389 390 @Override 391 public int indexOfChild(View view) { 392 return RecyclerView.this.indexOfChild(view); 393 } 394 395 @Override 396 public void removeViewAt(int index) { 397 final View child = RecyclerView.this.getChildAt(index); 398 if (child != null) { 399 dispatchChildDetached(child); 400 } 401 RecyclerView.this.removeViewAt(index); 402 } 403 404 @Override 405 public View getChildAt(int offset) { 406 return RecyclerView.this.getChildAt(offset); 407 } 408 409 @Override 410 public void removeAllViews() { 411 final int count = getChildCount(); 412 for (int i = 0; i < count; i ++) { 413 dispatchChildDetached(getChildAt(i)); 414 } 415 RecyclerView.this.removeAllViews(); 416 } 417 418 @Override 419 public ViewHolder getChildViewHolder(View view) { 420 return getChildViewHolderInt(view); 421 } 422 423 @Override 424 public void attachViewToParent(View child, int index, 425 ViewGroup.LayoutParams layoutParams) { 426 final ViewHolder vh = getChildViewHolderInt(child); 427 if (vh != null) { 428 if (!vh.isTmpDetached() && !vh.shouldIgnore()) { 429 throw new IllegalArgumentException("Called attach on a child which is not" 430 + " detached: " + vh); 431 } 432 if (DEBUG) { 433 Log.d(TAG, "reAttach " + vh); 434 } 435 vh.clearTmpDetachFlag(); 436 } 437 RecyclerView.this.attachViewToParent(child, index, layoutParams); 438 } 439 440 @Override 441 public void detachViewFromParent(int offset) { 442 final View view = getChildAt(offset); 443 if (view != null) { 444 final ViewHolder vh = getChildViewHolderInt(view); 445 if (vh != null) { 446 if (vh.isTmpDetached() && !vh.shouldIgnore()) { 447 throw new IllegalArgumentException("called detach on an already" 448 + " detached child " + vh); 449 } 450 if (DEBUG) { 451 Log.d(TAG, "tmpDetach " + vh); 452 } 453 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); 454 } 455 } 456 RecyclerView.this.detachViewFromParent(offset); 457 } 458 }); 459 } 460 461 void initAdapterManager() { 462 mAdapterHelper = new AdapterHelper(new Callback() { 463 @Override 464 public ViewHolder findViewHolder(int position) { 465 final ViewHolder vh = findViewHolderForPosition(position, true); 466 if (vh == null) { 467 return null; 468 } 469 // ensure it is not hidden because for adapter helper, the only thing matter is that 470 // LM thinks view is a child. 471 if (mChildHelper.isHidden(vh.itemView)) { 472 if (DEBUG) { 473 Log.d(TAG, "assuming view holder cannot be find because it is hidden"); 474 } 475 return null; 476 } 477 return vh; 478 } 479 480 @Override 481 public void offsetPositionsForRemovingInvisible(int start, int count) { 482 offsetPositionRecordsForRemove(start, count, true); 483 mItemsAddedOrRemoved = true; 484 mState.mDeletedInvisibleItemCountSincePreviousLayout += count; 485 } 486 487 @Override 488 public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount) { 489 offsetPositionRecordsForRemove(positionStart, itemCount, false); 490 mItemsAddedOrRemoved = true; 491 } 492 493 @Override 494 public void markViewHoldersUpdated(int positionStart, int itemCount) { 495 viewRangeUpdate(positionStart, itemCount); 496 mItemsChanged = true; 497 } 498 499 @Override 500 public void onDispatchFirstPass(UpdateOp op) { 501 dispatchUpdate(op); 502 } 503 504 void dispatchUpdate(UpdateOp op) { 505 switch (op.cmd) { 506 case UpdateOp.ADD: 507 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); 508 break; 509 case UpdateOp.REMOVE: 510 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); 511 break; 512 case UpdateOp.UPDATE: 513 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount); 514 break; 515 case UpdateOp.MOVE: 516 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); 517 break; 518 } 519 } 520 521 @Override 522 public void onDispatchSecondPass(UpdateOp op) { 523 dispatchUpdate(op); 524 } 525 526 @Override 527 public void offsetPositionsForAdd(int positionStart, int itemCount) { 528 offsetPositionRecordsForInsert(positionStart, itemCount); 529 mItemsAddedOrRemoved = true; 530 } 531 532 @Override 533 public void offsetPositionsForMove(int from, int to) { 534 offsetPositionRecordsForMove(from, to); 535 // should we create mItemsMoved ? 536 mItemsAddedOrRemoved = true; 537 } 538 }); 539 } 540 541 /** 542 * RecyclerView can perform several optimizations if it can know in advance that changes in 543 * adapter content cannot change the size of the RecyclerView itself. 544 * If your use of RecyclerView falls into this category, set this to true. 545 * 546 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 547 */ 548 public void setHasFixedSize(boolean hasFixedSize) { 549 mHasFixedSize = hasFixedSize; 550 } 551 552 /** 553 * @return true if the app has specified that changes in adapter content cannot change 554 * the size of the RecyclerView itself. 555 */ 556 public boolean hasFixedSize() { 557 return mHasFixedSize; 558 } 559 560 @Override 561 public void setClipToPadding(boolean clipToPadding) { 562 if (clipToPadding != mClipToPadding) { 563 invalidateGlows(); 564 } 565 mClipToPadding = clipToPadding; 566 super.setClipToPadding(clipToPadding); 567 if (mFirstLayoutComplete) { 568 requestLayout(); 569 } 570 } 571 572 /** 573 * Configure the scrolling touch slop for a specific use case. 574 * 575 * Set up the RecyclerView's scrolling motion threshold based on common usages. 576 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. 577 * 578 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing 579 * the intended usage of this RecyclerView 580 */ 581 public void setScrollingTouchSlop(int slopConstant) { 582 final ViewConfiguration vc = ViewConfiguration.get(getContext()); 583 switch (slopConstant) { 584 default: 585 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " 586 + slopConstant + "; using default value"); 587 // fall-through 588 case TOUCH_SLOP_DEFAULT: 589 mTouchSlop = vc.getScaledTouchSlop(); 590 break; 591 592 case TOUCH_SLOP_PAGING: 593 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc); 594 break; 595 } 596 } 597 598 /** 599 * Swaps the current adapter with the provided one. It is similar to 600 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same 601 * {@link ViewHolder} and does not clear the RecycledViewPool. 602 * <p> 603 * Note that it still calls onAdapterChanged callbacks. 604 * 605 * @param adapter The new adapter to set, or null to set no adapter. 606 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing 607 * Views. If adapters have stable ids and/or you want to 608 * animate the disappearing views, you may prefer to set 609 * this to false. 610 * @see #setAdapter(Adapter) 611 */ 612 public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) { 613 setAdapterInternal(adapter, true, removeAndRecycleExistingViews); 614 setDataSetChangedAfterLayout(); 615 requestLayout(); 616 } 617 /** 618 * Set a new adapter to provide child views on demand. 619 * <p> 620 * When adapter is changed, all existing views are recycled back to the pool. If the pool has 621 * only one adapter, it will be cleared. 622 * 623 * @param adapter The new adapter to set, or null to set no adapter. 624 * @see #swapAdapter(Adapter, boolean) 625 */ 626 public void setAdapter(Adapter adapter) { 627 setAdapterInternal(adapter, false, true); 628 requestLayout(); 629 } 630 631 /** 632 * Replaces the current adapter with the new one and triggers listeners. 633 * @param adapter The new adapter 634 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and 635 * item types with the current adapter (helps us avoid cache 636 * invalidation). 637 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If 638 * compatibleWithPrevious is false, this parameter is ignored. 639 */ 640 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, 641 boolean removeAndRecycleViews) { 642 if (mAdapter != null) { 643 mAdapter.unregisterAdapterDataObserver(mObserver); 644 mAdapter.onDetachedFromRecyclerView(this); 645 } 646 if (!compatibleWithPrevious || removeAndRecycleViews) { 647 // end all running animations 648 if (mItemAnimator != null) { 649 mItemAnimator.endAnimations(); 650 } 651 // Since animations are ended, mLayout.children should be equal to 652 // recyclerView.children. This may not be true if item animator's end does not work as 653 // expected. (e.g. not release children instantly). It is safer to use mLayout's child 654 // count. 655 if (mLayout != null) { 656 mLayout.removeAndRecycleAllViews(mRecycler); 657 mLayout.removeAndRecycleScrapInt(mRecycler); 658 } 659 // we should clear it here before adapters are swapped to ensure correct callbacks. 660 mRecycler.clear(); 661 } 662 mAdapterHelper.reset(); 663 final Adapter oldAdapter = mAdapter; 664 mAdapter = adapter; 665 if (adapter != null) { 666 adapter.registerAdapterDataObserver(mObserver); 667 adapter.onAttachedToRecyclerView(this); 668 } 669 if (mLayout != null) { 670 mLayout.onAdapterChanged(oldAdapter, mAdapter); 671 } 672 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 673 mState.mStructureChanged = true; 674 markKnownViewsInvalid(); 675 } 676 677 /** 678 * Retrieves the previously set adapter or null if no adapter is set. 679 * 680 * @return The previously set adapter 681 * @see #setAdapter(Adapter) 682 */ 683 public Adapter getAdapter() { 684 return mAdapter; 685 } 686 687 /** 688 * Register a listener that will be notified whenever a child view is recycled. 689 * 690 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 691 * that a child view is no longer needed. If an application associates expensive 692 * or heavyweight data with item views, this may be a good place to release 693 * or free those resources.</p> 694 * 695 * @param listener Listener to register, or null to clear 696 */ 697 public void setRecyclerListener(RecyclerListener listener) { 698 mRecyclerListener = listener; 699 } 700 701 /** 702 * <p>Return the offset of the RecyclerView's text baseline from the its top 703 * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, 704 * this method returns -1.</p> 705 * 706 * @return the offset of the baseline within the RecyclerView's bounds or -1 707 * if baseline alignment is not supported 708 */ 709 @Override 710 public int getBaseline() { 711 if (mLayout != null) { 712 return mLayout.getBaseline(); 713 } else { 714 return super.getBaseline(); 715 } 716 } 717 718 /** 719 * Set the {@link LayoutManager} that this RecyclerView will use. 720 * 721 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 722 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 723 * layout arrangements for child views. These arrangements are controlled by the 724 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 725 * 726 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 727 * 728 * @param layout LayoutManager to use 729 */ 730 public void setLayoutManager(LayoutManager layout) { 731 if (layout == mLayout) { 732 return; 733 } 734 // TODO We should do this switch a dispachLayout pass and animate children. There is a good 735 // chance that LayoutManagers will re-use views. 736 if (mLayout != null) { 737 if (mIsAttached) { 738 mLayout.onDetachedFromWindow(this, mRecycler); 739 } 740 mLayout.setRecyclerView(null); 741 } 742 mRecycler.clear(); 743 mChildHelper.removeAllViewsUnfiltered(); 744 mLayout = layout; 745 if (layout != null) { 746 if (layout.mRecyclerView != null) { 747 throw new IllegalArgumentException("LayoutManager " + layout + 748 " is already attached to a RecyclerView: " + layout.mRecyclerView); 749 } 750 mLayout.setRecyclerView(this); 751 if (mIsAttached) { 752 mLayout.onAttachedToWindow(this); 753 } 754 } 755 requestLayout(); 756 } 757 758 @Override 759 protected Parcelable onSaveInstanceState() { 760 SavedState state = new SavedState(super.onSaveInstanceState()); 761 if (mPendingSavedState != null) { 762 state.copyFrom(mPendingSavedState); 763 } else if (mLayout != null) { 764 state.mLayoutState = mLayout.onSaveInstanceState(); 765 } else { 766 state.mLayoutState = null; 767 } 768 769 return state; 770 } 771 772 @Override 773 protected void onRestoreInstanceState(Parcelable state) { 774 mPendingSavedState = (SavedState) state; 775 super.onRestoreInstanceState(mPendingSavedState.getSuperState()); 776 if (mLayout != null && mPendingSavedState.mLayoutState != null) { 777 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); 778 } 779 } 780 781 /** 782 * Adds a view to the animatingViews list. 783 * mAnimatingViews holds the child views that are currently being kept around 784 * purely for the purpose of being animated out of view. They are drawn as a regular 785 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager 786 * as they are managed separately from the regular child views. 787 * @param viewHolder The ViewHolder to be removed 788 */ 789 private void addAnimatingView(ViewHolder viewHolder) { 790 final View view = viewHolder.itemView; 791 final boolean alreadyParented = view.getParent() == this; 792 mRecycler.unscrapView(getChildViewHolder(view)); 793 if (viewHolder.isTmpDetached()) { 794 // re-attach 795 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); 796 } else if(!alreadyParented) { 797 mChildHelper.addView(view, true); 798 } else { 799 mChildHelper.hide(view); 800 } 801 } 802 803 /** 804 * Removes a view from the animatingViews list. 805 * @param view The view to be removed 806 * @see #addAnimatingView(RecyclerView.ViewHolder) 807 * @return true if an animating view is removed 808 */ 809 private boolean removeAnimatingView(View view) { 810 eatRequestLayout(); 811 final boolean removed = mChildHelper.removeViewIfHidden(view); 812 if (removed) { 813 final ViewHolder viewHolder = getChildViewHolderInt(view); 814 mRecycler.unscrapView(viewHolder); 815 mRecycler.recycleViewHolderInternal(viewHolder); 816 if (DEBUG) { 817 Log.d(TAG, "after removing animated view: " + view + ", " + this); 818 } 819 } 820 resumeRequestLayout(false); 821 return removed; 822 } 823 824 /** 825 * Return the {@link LayoutManager} currently responsible for 826 * layout policy for this RecyclerView. 827 * 828 * @return The currently bound LayoutManager 829 */ 830 public LayoutManager getLayoutManager() { 831 return mLayout; 832 } 833 834 /** 835 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 836 * if no pool is set for this view a new one will be created. See 837 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 838 * 839 * @return The pool used to store recycled item views for reuse. 840 * @see #setRecycledViewPool(RecycledViewPool) 841 */ 842 public RecycledViewPool getRecycledViewPool() { 843 return mRecycler.getRecycledViewPool(); 844 } 845 846 /** 847 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 848 * This can be useful if you have multiple RecyclerViews with adapters that use the same 849 * view types, for example if you have several data sets with the same kinds of item views 850 * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. 851 * 852 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 853 */ 854 public void setRecycledViewPool(RecycledViewPool pool) { 855 mRecycler.setRecycledViewPool(pool); 856 } 857 858 /** 859 * Sets a new {@link ViewCacheExtension} to be used by the Recycler. 860 * 861 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. 862 * 863 * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)} 864 */ 865 public void setViewCacheExtension(ViewCacheExtension extension) { 866 mRecycler.setViewCacheExtension(extension); 867 } 868 869 /** 870 * Set the number of offscreen views to retain before adding them to the potentially shared 871 * {@link #getRecycledViewPool() recycled view pool}. 872 * 873 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 874 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 875 * to rebind them.</p> 876 * 877 * @param size Number of views to cache offscreen before returning them to the general 878 * recycled view pool 879 */ 880 public void setItemViewCacheSize(int size) { 881 mRecycler.setViewCacheSize(size); 882 } 883 884 /** 885 * Return the current scrolling state of the RecyclerView. 886 * 887 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 888 * {@link #SCROLL_STATE_SETTLING} 889 */ 890 public int getScrollState() { 891 return mScrollState; 892 } 893 894 private void setScrollState(int state) { 895 if (state == mScrollState) { 896 return; 897 } 898 if (DEBUG) { 899 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, 900 new Exception()); 901 } 902 mScrollState = state; 903 if (state != SCROLL_STATE_SETTLING) { 904 stopScrollersInternal(); 905 } 906 dispatchOnScrollStateChanged(state); 907 } 908 909 /** 910 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 911 * affect both measurement and drawing of individual item views. 912 * 913 * <p>Item decorations are ordered. Decorations placed earlier in the list will 914 * be run/queried/drawn first for their effects on item views. Padding added to views 915 * will be nested; a padding added by an earlier decoration will mean further 916 * item decorations in the list will be asked to draw/pad within the previous decoration's 917 * given area.</p> 918 * 919 * @param decor Decoration to add 920 * @param index Position in the decoration chain to insert this decoration at. If this value 921 * is negative the decoration will be added at the end. 922 */ 923 public void addItemDecoration(ItemDecoration decor, int index) { 924 if (mLayout != null) { 925 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" 926 + " layout"); 927 } 928 if (mItemDecorations.isEmpty()) { 929 setWillNotDraw(false); 930 } 931 if (index < 0) { 932 mItemDecorations.add(decor); 933 } else { 934 mItemDecorations.add(index, decor); 935 } 936 markItemDecorInsetsDirty(); 937 requestLayout(); 938 } 939 940 /** 941 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 942 * affect both measurement and drawing of individual item views. 943 * 944 * <p>Item decorations are ordered. Decorations placed earlier in the list will 945 * be run/queried/drawn first for their effects on item views. Padding added to views 946 * will be nested; a padding added by an earlier decoration will mean further 947 * item decorations in the list will be asked to draw/pad within the previous decoration's 948 * given area.</p> 949 * 950 * @param decor Decoration to add 951 */ 952 public void addItemDecoration(ItemDecoration decor) { 953 addItemDecoration(decor, -1); 954 } 955 956 /** 957 * Remove an {@link ItemDecoration} from this RecyclerView. 958 * 959 * <p>The given decoration will no longer impact the measurement and drawing of 960 * item views.</p> 961 * 962 * @param decor Decoration to remove 963 * @see #addItemDecoration(ItemDecoration) 964 */ 965 public void removeItemDecoration(ItemDecoration decor) { 966 if (mLayout != null) { 967 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" 968 + " layout"); 969 } 970 mItemDecorations.remove(decor); 971 if (mItemDecorations.isEmpty()) { 972 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 973 } 974 markItemDecorInsetsDirty(); 975 requestLayout(); 976 } 977 978 /** 979 * Set a listener that will be notified of any changes in scroll state or position. 980 * 981 * @param listener Listener to set or null to clear 982 * 983 * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and 984 * {@link #removeOnScrollListener(OnScrollListener)} 985 */ 986 @Deprecated 987 public void setOnScrollListener(OnScrollListener listener) { 988 mScrollListener = listener; 989 } 990 991 /** 992 * Add a listener that will be notified of any changes in scroll state or position. 993 * 994 * <p>Components that add a listener should take care to remove it when finished. 995 * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} 996 * to remove all attached listeners.</p> 997 * 998 * @param listener listener to set or null to clear 999 */ 1000 public void addOnScrollListener(OnScrollListener listener) { 1001 if (mScrollListeners == null) { 1002 mScrollListeners = new ArrayList<OnScrollListener>(); 1003 } 1004 mScrollListeners.add(listener); 1005 } 1006 1007 /** 1008 * Remove a listener that was notified of any changes in scroll state or position. 1009 * 1010 * @param listener listener to set or null to clear 1011 */ 1012 public void removeOnScrollListener(OnScrollListener listener) { 1013 if (mScrollListeners != null) { 1014 mScrollListeners.remove(listener); 1015 } 1016 } 1017 1018 /** 1019 * Remove all secondary listener that were notified of any changes in scroll state or position. 1020 */ 1021 public void clearOnScrollListeners() { 1022 if (mScrollListeners != null) { 1023 mScrollListeners.clear(); 1024 } 1025 } 1026 1027 /** 1028 * Convenience method to scroll to a certain position. 1029 * 1030 * RecyclerView does not implement scrolling logic, rather forwards the call to 1031 * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)} 1032 * @param position Scroll to this adapter position 1033 * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int) 1034 */ 1035 public void scrollToPosition(int position) { 1036 stopScroll(); 1037 if (mLayout == null) { 1038 Log.e(TAG, "Cannot scroll to position a LayoutManager set. " + 1039 "Call setLayoutManager with a non-null argument."); 1040 return; 1041 } 1042 mLayout.scrollToPosition(position); 1043 awakenScrollBars(); 1044 } 1045 1046 /** 1047 * Starts a smooth scroll to an adapter position. 1048 * <p> 1049 * To support smooth scrolling, you must override 1050 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a 1051 * {@link SmoothScroller}. 1052 * <p> 1053 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to 1054 * provide a custom smooth scroll logic, override 1055 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your 1056 * LayoutManager. 1057 * 1058 * @param position The adapter position to scroll to 1059 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) 1060 */ 1061 public void smoothScrollToPosition(int position) { 1062 if (mLayout == null) { 1063 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + 1064 "Call setLayoutManager with a non-null argument."); 1065 return; 1066 } 1067 mLayout.smoothScrollToPosition(this, mState, position); 1068 } 1069 1070 @Override 1071 public void scrollTo(int x, int y) { 1072 throw new UnsupportedOperationException( 1073 "RecyclerView does not support scrolling to an absolute position."); 1074 } 1075 1076 @Override 1077 public void scrollBy(int x, int y) { 1078 if (mLayout == null) { 1079 Log.e(TAG, "Cannot scroll without a LayoutManager set. " + 1080 "Call setLayoutManager with a non-null argument."); 1081 return; 1082 } 1083 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1084 final boolean canScrollVertical = mLayout.canScrollVertically(); 1085 if (canScrollHorizontal || canScrollVertical) { 1086 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0); 1087 } 1088 } 1089 1090 /** 1091 * Helper method reflect data changes to the state. 1092 * <p> 1093 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change 1094 * but data actually changed. 1095 * <p> 1096 * This method consumes all deferred changes to avoid that case. 1097 */ 1098 private void consumePendingUpdateOperations() { 1099 mUpdateChildViewsRunnable.run(); 1100 } 1101 1102 /** 1103 * Does not perform bounds checking. Used by internal methods that have already validated input. 1104 * 1105 * @return Whether any scroll was consumed in either direction. 1106 */ 1107 boolean scrollByInternal(int x, int y) { 1108 int overscrollX = 0, overscrollY = 0; 1109 int hresult = 0, vresult = 0; 1110 consumePendingUpdateOperations(); 1111 if (mAdapter != null) { 1112 eatRequestLayout(); 1113 mRunningLayoutOrScroll = true; 1114 if (x != 0) { 1115 hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); 1116 overscrollX = x - hresult; 1117 } 1118 if (y != 0) { 1119 vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); 1120 overscrollY = y - vresult; 1121 } 1122 if (supportsChangeAnimations()) { 1123 // Fix up shadow views used by changing animations 1124 int count = mChildHelper.getChildCount(); 1125 for (int i = 0; i < count; i++) { 1126 View view = mChildHelper.getChildAt(i); 1127 ViewHolder holder = getChildViewHolder(view); 1128 if (holder != null && holder.mShadowingHolder != null) { 1129 ViewHolder shadowingHolder = holder.mShadowingHolder; 1130 View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null; 1131 if (shadowingView != null) { 1132 int left = view.getLeft(); 1133 int top = view.getTop(); 1134 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { 1135 shadowingView.layout(left, top, 1136 left + shadowingView.getWidth(), 1137 top + shadowingView.getHeight()); 1138 } 1139 } 1140 } 1141 } 1142 } 1143 mRunningLayoutOrScroll = false; 1144 resumeRequestLayout(false); 1145 } 1146 if (!mItemDecorations.isEmpty()) { 1147 invalidate(); 1148 } 1149 if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { 1150 considerReleasingGlowsOnScroll(x, y); 1151 pullGlows(overscrollX, overscrollY); 1152 } 1153 if (hresult != 0 || vresult != 0) { 1154 dispatchOnScrolled(hresult, vresult); 1155 } 1156 if (!awakenScrollBars()) { 1157 invalidate(); 1158 } 1159 return hresult != 0 || vresult != 0; 1160 } 1161 1162 /** 1163 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal 1164 * range. This value is used to compute the length of the thumb within the scrollbar's track. 1165 * </p> 1166 * 1167 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1168 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> 1169 * 1170 * <p>Default implementation returns 0.</p> 1171 * 1172 * <p>If you want to support scroll bars, override 1173 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your 1174 * LayoutManager. </p> 1175 * 1176 * @return The horizontal offset of the scrollbar's thumb 1177 * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset 1178 * (RecyclerView.Adapter) 1179 */ 1180 @Override 1181 public int computeHorizontalScrollOffset() { 1182 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) 1183 : 0; 1184 } 1185 1186 /** 1187 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the 1188 * horizontal range. This value is used to compute the length of the thumb within the 1189 * scrollbar's track.</p> 1190 * 1191 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1192 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> 1193 * 1194 * <p>Default implementation returns 0.</p> 1195 * 1196 * <p>If you want to support scroll bars, override 1197 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your 1198 * LayoutManager.</p> 1199 * 1200 * @return The horizontal extent of the scrollbar's thumb 1201 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) 1202 */ 1203 @Override 1204 public int computeHorizontalScrollExtent() { 1205 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; 1206 } 1207 1208 /** 1209 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> 1210 * 1211 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1212 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> 1213 * 1214 * <p>Default implementation returns 0.</p> 1215 * 1216 * <p>If you want to support scroll bars, override 1217 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your 1218 * LayoutManager.</p> 1219 * 1220 * @return The total horizontal range represented by the vertical scrollbar 1221 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) 1222 */ 1223 @Override 1224 public int computeHorizontalScrollRange() { 1225 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; 1226 } 1227 1228 /** 1229 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. 1230 * This value is used to compute the length of the thumb within the scrollbar's track. </p> 1231 * 1232 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1233 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> 1234 * 1235 * <p>Default implementation returns 0.</p> 1236 * 1237 * <p>If you want to support scroll bars, override 1238 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your 1239 * LayoutManager.</p> 1240 * 1241 * @return The vertical offset of the scrollbar's thumb 1242 * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset 1243 * (RecyclerView.Adapter) 1244 */ 1245 @Override 1246 public int computeVerticalScrollOffset() { 1247 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; 1248 } 1249 1250 /** 1251 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. 1252 * This value is used to compute the length of the thumb within the scrollbar's track.</p> 1253 * 1254 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1255 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> 1256 * 1257 * <p>Default implementation returns 0.</p> 1258 * 1259 * <p>If you want to support scroll bars, override 1260 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your 1261 * LayoutManager.</p> 1262 * 1263 * @return The vertical extent of the scrollbar's thumb 1264 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) 1265 */ 1266 @Override 1267 public int computeVerticalScrollExtent() { 1268 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; 1269 } 1270 1271 /** 1272 * <p>Compute the vertical range that the vertical scrollbar represents.</p> 1273 * 1274 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1275 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> 1276 * 1277 * <p>Default implementation returns 0.</p> 1278 * 1279 * <p>If you want to support scroll bars, override 1280 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your 1281 * LayoutManager.</p> 1282 * 1283 * @return The total vertical range represented by the vertical scrollbar 1284 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) 1285 */ 1286 @Override 1287 public int computeVerticalScrollRange() { 1288 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; 1289 } 1290 1291 1292 void eatRequestLayout() { 1293 if (!mEatRequestLayout) { 1294 mEatRequestLayout = true; 1295 mLayoutRequestEaten = false; 1296 } 1297 } 1298 1299 void resumeRequestLayout(boolean performLayoutChildren) { 1300 if (mEatRequestLayout) { 1301 if (performLayoutChildren && mLayoutRequestEaten && 1302 mLayout != null && mAdapter != null) { 1303 dispatchLayout(); 1304 } 1305 mEatRequestLayout = false; 1306 mLayoutRequestEaten = false; 1307 } 1308 } 1309 1310 /** 1311 * Animate a scroll by the given amount of pixels along either axis. 1312 * 1313 * @param dx Pixels to scroll horizontally 1314 * @param dy Pixels to scroll vertically 1315 */ 1316 public void smoothScrollBy(int dx, int dy) { 1317 if (mLayout == null) { 1318 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + 1319 "Call setLayoutManager with a non-null argument."); 1320 return; 1321 } 1322 if (!mLayout.canScrollHorizontally()) { 1323 dx = 0; 1324 } 1325 if (!mLayout.canScrollVertically()) { 1326 dy = 0; 1327 } 1328 if (dx != 0 || dy != 0) { 1329 mViewFlinger.smoothScrollBy(dx, dy); 1330 } 1331 } 1332 1333 /** 1334 * Begin a standard fling with an initial velocity along each axis in pixels per second. 1335 * If the velocity given is below the system-defined minimum this method will return false 1336 * and no fling will occur. 1337 * 1338 * @param velocityX Initial horizontal velocity in pixels per second 1339 * @param velocityY Initial vertical velocity in pixels per second 1340 * @return true if the fling was started, false if the velocity was too low to fling or 1341 * LayoutManager does not support scrolling in the axis fling is issued. 1342 * 1343 * @see LayoutManager#canScrollVertically() 1344 * @see LayoutManager#canScrollHorizontally() 1345 */ 1346 public boolean fling(int velocityX, int velocityY) { 1347 if (mLayout == null) { 1348 Log.e(TAG, "Cannot fling without a LayoutManager set. " + 1349 "Call setLayoutManager with a non-null argument."); 1350 return false; 1351 } 1352 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1353 final boolean canScrollVertical = mLayout.canScrollVertically(); 1354 if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { 1355 velocityX = 0; 1356 } 1357 if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { 1358 velocityY = 0; 1359 } 1360 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 1361 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 1362 if (velocityX != 0 || velocityY != 0) { 1363 mViewFlinger.fling(velocityX, velocityY); 1364 return true; 1365 } 1366 return false; 1367 } 1368 1369 /** 1370 * Stop any current scroll in progress, such as one started by 1371 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 1372 */ 1373 public void stopScroll() { 1374 setScrollState(SCROLL_STATE_IDLE); 1375 stopScrollersInternal(); 1376 } 1377 1378 /** 1379 * Similar to {@link #stopScroll()} but does not set the state. 1380 */ 1381 private void stopScrollersInternal() { 1382 mViewFlinger.stop(); 1383 if (mLayout != null) { 1384 mLayout.stopSmoothScroller(); 1385 } 1386 } 1387 1388 /** 1389 * Apply a pull to relevant overscroll glow effects 1390 */ 1391 private void pullGlows(int overscrollX, int overscrollY) { 1392 if (overscrollX < 0) { 1393 ensureLeftGlow(); 1394 mLeftGlow.onPull(-overscrollX / (float) getWidth()); 1395 } else if (overscrollX > 0) { 1396 ensureRightGlow(); 1397 mRightGlow.onPull(overscrollX / (float) getWidth()); 1398 } 1399 1400 if (overscrollY < 0) { 1401 ensureTopGlow(); 1402 mTopGlow.onPull(-overscrollY / (float) getHeight()); 1403 } else if (overscrollY > 0) { 1404 ensureBottomGlow(); 1405 mBottomGlow.onPull(overscrollY / (float) getHeight()); 1406 } 1407 1408 if (overscrollX != 0 || overscrollY != 0) { 1409 ViewCompat.postInvalidateOnAnimation(this); 1410 } 1411 } 1412 1413 private void releaseGlows() { 1414 boolean needsInvalidate = false; 1415 if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease(); 1416 if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease(); 1417 if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease(); 1418 if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease(); 1419 if (needsInvalidate) { 1420 ViewCompat.postInvalidateOnAnimation(this); 1421 } 1422 } 1423 1424 private void considerReleasingGlowsOnScroll(int dx, int dy) { 1425 boolean needsInvalidate = false; 1426 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { 1427 needsInvalidate = mLeftGlow.onRelease(); 1428 } 1429 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { 1430 needsInvalidate |= mRightGlow.onRelease(); 1431 } 1432 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { 1433 needsInvalidate |= mTopGlow.onRelease(); 1434 } 1435 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { 1436 needsInvalidate |= mBottomGlow.onRelease(); 1437 } 1438 if (needsInvalidate) { 1439 ViewCompat.postInvalidateOnAnimation(this); 1440 } 1441 } 1442 1443 void absorbGlows(int velocityX, int velocityY) { 1444 if (velocityX < 0) { 1445 ensureLeftGlow(); 1446 mLeftGlow.onAbsorb(-velocityX); 1447 } else if (velocityX > 0) { 1448 ensureRightGlow(); 1449 mRightGlow.onAbsorb(velocityX); 1450 } 1451 1452 if (velocityY < 0) { 1453 ensureTopGlow(); 1454 mTopGlow.onAbsorb(-velocityY); 1455 } else if (velocityY > 0) { 1456 ensureBottomGlow(); 1457 mBottomGlow.onAbsorb(velocityY); 1458 } 1459 1460 if (velocityX != 0 || velocityY != 0) { 1461 ViewCompat.postInvalidateOnAnimation(this); 1462 } 1463 } 1464 1465 void ensureLeftGlow() { 1466 if (mLeftGlow != null) { 1467 return; 1468 } 1469 mLeftGlow = new EdgeEffectCompat(getContext()); 1470 if (mClipToPadding) { 1471 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 1472 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 1473 } else { 1474 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 1475 } 1476 } 1477 1478 void ensureRightGlow() { 1479 if (mRightGlow != null) { 1480 return; 1481 } 1482 mRightGlow = new EdgeEffectCompat(getContext()); 1483 if (mClipToPadding) { 1484 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 1485 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 1486 } else { 1487 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 1488 } 1489 } 1490 1491 void ensureTopGlow() { 1492 if (mTopGlow != null) { 1493 return; 1494 } 1495 mTopGlow = new EdgeEffectCompat(getContext()); 1496 if (mClipToPadding) { 1497 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 1498 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 1499 } else { 1500 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 1501 } 1502 1503 } 1504 1505 void ensureBottomGlow() { 1506 if (mBottomGlow != null) { 1507 return; 1508 } 1509 mBottomGlow = new EdgeEffectCompat(getContext()); 1510 if (mClipToPadding) { 1511 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 1512 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 1513 } else { 1514 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 1515 } 1516 } 1517 1518 void invalidateGlows() { 1519 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; 1520 } 1521 1522 // Focus handling 1523 1524 @Override 1525 public View focusSearch(View focused, int direction) { 1526 View result = mLayout.onInterceptFocusSearch(focused, direction); 1527 if (result != null) { 1528 return result; 1529 } 1530 final FocusFinder ff = FocusFinder.getInstance(); 1531 result = ff.findNextFocus(this, focused, direction); 1532 if (result == null && mAdapter != null && mLayout != null) { 1533 eatRequestLayout(); 1534 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 1535 resumeRequestLayout(false); 1536 } 1537 return result != null ? result : super.focusSearch(focused, direction); 1538 } 1539 1540 @Override 1541 public void requestChildFocus(View child, View focused) { 1542 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { 1543 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 1544 1545 // get item decor offsets w/o refreshing. If they are invalid, there will be another 1546 // layout pass to fix them, then it is LayoutManager's responsibility to keep focused 1547 // View in viewport. 1548 final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams(); 1549 if (focusedLayoutParams instanceof LayoutParams) { 1550 // if focused child has item decors, use them. Otherwise, ignore. 1551 final LayoutParams lp = (LayoutParams) focusedLayoutParams; 1552 if (!lp.mInsetsDirty) { 1553 final Rect insets = lp.mDecorInsets; 1554 mTempRect.left -= insets.left; 1555 mTempRect.right += insets.right; 1556 mTempRect.top -= insets.top; 1557 mTempRect.bottom += insets.bottom; 1558 } 1559 } 1560 1561 offsetDescendantRectToMyCoords(focused, mTempRect); 1562 offsetRectIntoDescendantCoords(child, mTempRect); 1563 requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete); 1564 } 1565 super.requestChildFocus(child, focused); 1566 } 1567 1568 @Override 1569 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 1570 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); 1571 } 1572 1573 @Override 1574 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1575 if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { 1576 super.addFocusables(views, direction, focusableMode); 1577 } 1578 } 1579 1580 @Override 1581 protected void onAttachedToWindow() { 1582 super.onAttachedToWindow(); 1583 mIsAttached = true; 1584 mFirstLayoutComplete = false; 1585 if (mLayout != null) { 1586 mLayout.onAttachedToWindow(this); 1587 } 1588 mPostedAnimatorRunner = false; 1589 } 1590 1591 @Override 1592 protected void onDetachedFromWindow() { 1593 super.onDetachedFromWindow(); 1594 if (mItemAnimator != null) { 1595 mItemAnimator.endAnimations(); 1596 } 1597 mFirstLayoutComplete = false; 1598 1599 stopScroll(); 1600 mIsAttached = false; 1601 if (mLayout != null) { 1602 mLayout.onDetachedFromWindow(this, mRecycler); 1603 } 1604 removeCallbacks(mItemAnimatorRunner); 1605 } 1606 1607 /** 1608 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 1609 * {@link IllegalStateException} if it <b>is not</b>. 1610 * 1611 * @param message The message for the exception. Can be null. 1612 * @see #assertNotInLayoutOrScroll(String) 1613 */ 1614 void assertInLayoutOrScroll(String message) { 1615 if (!mRunningLayoutOrScroll) { 1616 if (message == null) { 1617 throw new IllegalStateException("Cannot call this method unless RecyclerView is " 1618 + "computing a layout or scrolling"); 1619 } 1620 throw new IllegalStateException(message); 1621 1622 } 1623 } 1624 1625 /** 1626 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 1627 * {@link IllegalStateException} if it <b>is</b>. 1628 * 1629 * @param message The message for the exception. Can be null. 1630 * @see #assertInLayoutOrScroll(String) 1631 */ 1632 void assertNotInLayoutOrScroll(String message) { 1633 if (mRunningLayoutOrScroll) { 1634 if (message == null) { 1635 throw new IllegalStateException("Cannot call this method while RecyclerView is " 1636 + "computing a layout or scrolling"); 1637 } 1638 throw new IllegalStateException(message); 1639 } 1640 } 1641 1642 /** 1643 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 1644 * to child views or this view's standard scrolling behavior. 1645 * 1646 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 1647 * returns true from 1648 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 1649 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 1650 * for each incoming MotionEvent until the end of the gesture.</p> 1651 * 1652 * @param listener Listener to add 1653 */ 1654 public void addOnItemTouchListener(OnItemTouchListener listener) { 1655 mOnItemTouchListeners.add(listener); 1656 } 1657 1658 /** 1659 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 1660 * 1661 * @param listener Listener to remove 1662 */ 1663 public void removeOnItemTouchListener(OnItemTouchListener listener) { 1664 mOnItemTouchListeners.remove(listener); 1665 if (mActiveOnItemTouchListener == listener) { 1666 mActiveOnItemTouchListener = null; 1667 } 1668 } 1669 1670 private boolean dispatchOnItemTouchIntercept(MotionEvent e) { 1671 final int action = e.getAction(); 1672 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { 1673 mActiveOnItemTouchListener = null; 1674 } 1675 1676 final int listenerCount = mOnItemTouchListeners.size(); 1677 for (int i = 0; i < listenerCount; i++) { 1678 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 1679 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 1680 mActiveOnItemTouchListener = listener; 1681 return true; 1682 } 1683 } 1684 return false; 1685 } 1686 1687 private boolean dispatchOnItemTouch(MotionEvent e) { 1688 final int action = e.getAction(); 1689 if (mActiveOnItemTouchListener != null) { 1690 if (action == MotionEvent.ACTION_DOWN) { 1691 // Stale state from a previous gesture, we're starting a new one. Clear it. 1692 mActiveOnItemTouchListener = null; 1693 } else { 1694 mActiveOnItemTouchListener.onTouchEvent(this, e); 1695 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1696 // Clean up for the next gesture. 1697 mActiveOnItemTouchListener = null; 1698 } 1699 return true; 1700 } 1701 } 1702 1703 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept 1704 // as called from onInterceptTouchEvent; skip it. 1705 if (action != MotionEvent.ACTION_DOWN) { 1706 final int listenerCount = mOnItemTouchListeners.size(); 1707 for (int i = 0; i < listenerCount; i++) { 1708 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 1709 if (listener.onInterceptTouchEvent(this, e)) { 1710 mActiveOnItemTouchListener = listener; 1711 return true; 1712 } 1713 } 1714 } 1715 return false; 1716 } 1717 1718 @Override 1719 public boolean onInterceptTouchEvent(MotionEvent e) { 1720 if (dispatchOnItemTouchIntercept(e)) { 1721 cancelTouch(); 1722 return true; 1723 } 1724 1725 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 1726 final boolean canScrollVertically = mLayout.canScrollVertically(); 1727 1728 if (mVelocityTracker == null) { 1729 mVelocityTracker = VelocityTracker.obtain(); 1730 } 1731 mVelocityTracker.addMovement(e); 1732 1733 final int action = MotionEventCompat.getActionMasked(e); 1734 final int actionIndex = MotionEventCompat.getActionIndex(e); 1735 1736 switch (action) { 1737 case MotionEvent.ACTION_DOWN: 1738 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 1739 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 1740 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 1741 1742 if (mScrollState == SCROLL_STATE_SETTLING) { 1743 getParent().requestDisallowInterceptTouchEvent(true); 1744 setScrollState(SCROLL_STATE_DRAGGING); 1745 } 1746 break; 1747 1748 case MotionEventCompat.ACTION_POINTER_DOWN: 1749 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 1750 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 1751 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 1752 break; 1753 1754 case MotionEvent.ACTION_MOVE: { 1755 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 1756 if (index < 0) { 1757 Log.e(TAG, "Error processing scroll; pointer index for id " + 1758 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 1759 return false; 1760 } 1761 1762 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 1763 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 1764 if (mScrollState != SCROLL_STATE_DRAGGING) { 1765 final int dx = x - mInitialTouchX; 1766 final int dy = y - mInitialTouchY; 1767 boolean startScroll = false; 1768 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 1769 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 1770 startScroll = true; 1771 } 1772 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 1773 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 1774 startScroll = true; 1775 } 1776 if (startScroll) { 1777 setScrollState(SCROLL_STATE_DRAGGING); 1778 } 1779 } 1780 } break; 1781 1782 case MotionEventCompat.ACTION_POINTER_UP: { 1783 onPointerUp(e); 1784 } break; 1785 1786 case MotionEvent.ACTION_UP: { 1787 mVelocityTracker.clear(); 1788 } break; 1789 1790 case MotionEvent.ACTION_CANCEL: { 1791 cancelTouch(); 1792 } 1793 } 1794 return mScrollState == SCROLL_STATE_DRAGGING; 1795 } 1796 1797 @Override 1798 public boolean onTouchEvent(MotionEvent e) { 1799 if (dispatchOnItemTouch(e)) { 1800 cancelTouch(); 1801 return true; 1802 } 1803 1804 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 1805 final boolean canScrollVertically = mLayout.canScrollVertically(); 1806 1807 if (mVelocityTracker == null) { 1808 mVelocityTracker = VelocityTracker.obtain(); 1809 } 1810 mVelocityTracker.addMovement(e); 1811 1812 final int action = MotionEventCompat.getActionMasked(e); 1813 final int actionIndex = MotionEventCompat.getActionIndex(e); 1814 1815 switch (action) { 1816 case MotionEvent.ACTION_DOWN: { 1817 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 1818 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 1819 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 1820 } break; 1821 1822 case MotionEventCompat.ACTION_POINTER_DOWN: { 1823 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 1824 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 1825 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 1826 } break; 1827 1828 case MotionEvent.ACTION_MOVE: { 1829 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 1830 if (index < 0) { 1831 Log.e(TAG, "Error processing scroll; pointer index for id " + 1832 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 1833 return false; 1834 } 1835 1836 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 1837 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 1838 if (mScrollState != SCROLL_STATE_DRAGGING) { 1839 final int dx = x - mInitialTouchX; 1840 final int dy = y - mInitialTouchY; 1841 boolean startScroll = false; 1842 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 1843 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 1844 startScroll = true; 1845 } 1846 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 1847 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 1848 startScroll = true; 1849 } 1850 if (startScroll) { 1851 setScrollState(SCROLL_STATE_DRAGGING); 1852 } 1853 } 1854 if (mScrollState == SCROLL_STATE_DRAGGING) { 1855 final int dx = x - mLastTouchX; 1856 final int dy = y - mLastTouchY; 1857 if (scrollByInternal( 1858 canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) { 1859 getParent().requestDisallowInterceptTouchEvent(true); 1860 } 1861 } 1862 mLastTouchX = x; 1863 mLastTouchY = y; 1864 } break; 1865 1866 case MotionEventCompat.ACTION_POINTER_UP: { 1867 onPointerUp(e); 1868 } break; 1869 1870 case MotionEvent.ACTION_UP: { 1871 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 1872 final float xvel = canScrollHorizontally ? 1873 -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0; 1874 final float yvel = canScrollVertically ? 1875 -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0; 1876 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 1877 setScrollState(SCROLL_STATE_IDLE); 1878 } 1879 mVelocityTracker.clear(); 1880 releaseGlows(); 1881 } break; 1882 1883 case MotionEvent.ACTION_CANCEL: { 1884 cancelTouch(); 1885 } break; 1886 } 1887 1888 return true; 1889 } 1890 1891 private void cancelTouch() { 1892 if (mVelocityTracker != null) { 1893 mVelocityTracker.clear(); 1894 } 1895 releaseGlows(); 1896 setScrollState(SCROLL_STATE_IDLE); 1897 } 1898 1899 private void onPointerUp(MotionEvent e) { 1900 final int actionIndex = MotionEventCompat.getActionIndex(e); 1901 if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) { 1902 // Pick a new pointer to pick up the slack. 1903 final int newIndex = actionIndex == 0 ? 1 : 0; 1904 mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex); 1905 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f); 1906 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f); 1907 } 1908 } 1909 1910 // @Override 1911 public boolean onGenericMotionEvent(MotionEvent event) { 1912 if (mLayout == null) { 1913 return false; 1914 } 1915 if ((MotionEventCompat.getSource(event) & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { 1916 if (event.getAction() == MotionEventCompat.ACTION_SCROLL) { 1917 final float vScroll, hScroll; 1918 if (mLayout.canScrollVertically()) { 1919 vScroll = MotionEventCompat 1920 .getAxisValue(event, MotionEventCompat.AXIS_VSCROLL); 1921 } else { 1922 vScroll = 0f; 1923 } 1924 if (mLayout.canScrollHorizontally()) { 1925 hScroll = MotionEventCompat 1926 .getAxisValue(event, MotionEventCompat.AXIS_HSCROLL); 1927 } else { 1928 hScroll = 0f; 1929 } 1930 1931 if (vScroll != 0 || hScroll != 0) { 1932 final float scrollFactor = getScrollFactor(); 1933 scrollBy((int) (hScroll * scrollFactor), (int) (vScroll * scrollFactor)); 1934 } 1935 } 1936 } 1937 return false; 1938 } 1939 1940 /** 1941 * Ported from View.getVerticalScrollFactor. 1942 */ 1943 private float getScrollFactor() { 1944 if (mScrollFactor == Float.MIN_VALUE) { 1945 TypedValue outValue = new TypedValue(); 1946 if (getContext().getTheme().resolveAttribute( 1947 android.R.attr.listPreferredItemHeight, outValue, true)) { 1948 mScrollFactor = outValue.getDimension( 1949 getContext().getResources().getDisplayMetrics()); 1950 } else { 1951 return 0; //listPreferredItemHeight is not defined, no generic scrolling 1952 } 1953 1954 } 1955 return mScrollFactor; 1956 } 1957 1958 @Override 1959 protected void onMeasure(int widthSpec, int heightSpec) { 1960 if (mAdapterUpdateDuringMeasure) { 1961 eatRequestLayout(); 1962 processAdapterUpdatesAndSetAnimationFlags(); 1963 1964 if (mState.mRunPredictiveAnimations) { 1965 // TODO: try to provide a better approach. 1966 // When RV decides to run predictive animations, we need to measure in pre-layout 1967 // state so that pre-layout pass results in correct layout. 1968 // On the other hand, this will prevent the layout manager from resizing properly. 1969 mState.mInPreLayout = true; 1970 } else { 1971 // consume remaining updates to provide a consistent state with the layout pass. 1972 mAdapterHelper.consumeUpdatesInOnePass(); 1973 mState.mInPreLayout = false; 1974 } 1975 mAdapterUpdateDuringMeasure = false; 1976 resumeRequestLayout(false); 1977 } 1978 1979 if (mAdapter != null) { 1980 mState.mItemCount = mAdapter.getItemCount(); 1981 } else { 1982 mState.mItemCount = 0; 1983 } 1984 if (mLayout == null) { 1985 defaultOnMeasure(widthSpec, heightSpec); 1986 } else { 1987 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 1988 } 1989 1990 mState.mInPreLayout = false; // clear 1991 } 1992 1993 /** 1994 * Used when onMeasure is called before layout manager is set 1995 */ 1996 private void defaultOnMeasure(int widthSpec, int heightSpec) { 1997 final int widthMode = MeasureSpec.getMode(widthSpec); 1998 final int heightMode = MeasureSpec.getMode(heightSpec); 1999 final int widthSize = MeasureSpec.getSize(widthSpec); 2000 final int heightSize = MeasureSpec.getSize(heightSpec); 2001 2002 int width = 0; 2003 int height = 0; 2004 2005 switch (widthMode) { 2006 case MeasureSpec.EXACTLY: 2007 case MeasureSpec.AT_MOST: 2008 width = widthSize; 2009 break; 2010 case MeasureSpec.UNSPECIFIED: 2011 default: 2012 width = ViewCompat.getMinimumWidth(this); 2013 break; 2014 } 2015 2016 switch (heightMode) { 2017 case MeasureSpec.EXACTLY: 2018 case MeasureSpec.AT_MOST: 2019 height = heightSize; 2020 break; 2021 case MeasureSpec.UNSPECIFIED: 2022 default: 2023 height = ViewCompat.getMinimumHeight(this); 2024 break; 2025 } 2026 2027 setMeasuredDimension(width, height); 2028 } 2029 2030 @Override 2031 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2032 super.onSizeChanged(w, h, oldw, oldh); 2033 if (w != oldw || h != oldh) { 2034 invalidateGlows(); 2035 } 2036 } 2037 2038 /** 2039 * Sets the {@link ItemAnimator} that will handle animations involving changes 2040 * to the items in this RecyclerView. By default, RecyclerView instantiates and 2041 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are 2042 * enabled for the RecyclerView depends on the ItemAnimator and whether 2043 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() 2044 * supports item animations}. 2045 * 2046 * @param animator The ItemAnimator being set. If null, no animations will occur 2047 * when changes occur to the items in this RecyclerView. 2048 */ 2049 public void setItemAnimator(ItemAnimator animator) { 2050 if (mItemAnimator != null) { 2051 mItemAnimator.endAnimations(); 2052 mItemAnimator.setListener(null); 2053 } 2054 mItemAnimator = animator; 2055 if (mItemAnimator != null) { 2056 mItemAnimator.setListener(mItemAnimatorListener); 2057 } 2058 } 2059 2060 /** 2061 * Gets the current ItemAnimator for this RecyclerView. A null return value 2062 * indicates that there is no animator and that item changes will happen without 2063 * any animations. By default, RecyclerView instantiates and 2064 * uses an instance of {@link DefaultItemAnimator}. 2065 * 2066 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur 2067 * when changes occur to the items in this RecyclerView. 2068 */ 2069 public ItemAnimator getItemAnimator() { 2070 return mItemAnimator; 2071 } 2072 2073 private boolean supportsChangeAnimations() { 2074 return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations(); 2075 } 2076 2077 /** 2078 * Post a runnable to the next frame to run pending item animations. Only the first such 2079 * request will be posted, governed by the mPostedAnimatorRunner flag. 2080 */ 2081 private void postAnimationRunner() { 2082 if (!mPostedAnimatorRunner && mIsAttached) { 2083 ViewCompat.postOnAnimation(this, mItemAnimatorRunner); 2084 mPostedAnimatorRunner = true; 2085 } 2086 } 2087 2088 private boolean predictiveItemAnimationsEnabled() { 2089 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); 2090 } 2091 2092 /** 2093 * Consumes adapter updates and calculates which type of animations we want to run. 2094 * Called in onMeasure and dispatchLayout. 2095 * <p> 2096 * This method may process only the pre-layout state of updates or all of them. 2097 */ 2098 private void processAdapterUpdatesAndSetAnimationFlags() { 2099 if (mDataSetHasChangedAfterLayout) { 2100 // Processing these items have no value since data set changed unexpectedly. 2101 // Instead, we just reset it. 2102 mAdapterHelper.reset(); 2103 markKnownViewsInvalid(); 2104 mLayout.onItemsChanged(this); 2105 } 2106 // simple animations are a subset of advanced animations (which will cause a 2107 // pre-layout step) 2108 // If layout supports predictive animations, pre-process to decide if we want to run them 2109 if (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) { 2110 mAdapterHelper.preProcess(); 2111 } else { 2112 mAdapterHelper.consumeUpdatesInOnePass(); 2113 } 2114 boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) || 2115 (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations())); 2116 mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && 2117 (mDataSetHasChangedAfterLayout || animationTypeSupported || 2118 mLayout.mRequestedSimpleAnimations) && 2119 (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); 2120 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && 2121 animationTypeSupported && !mDataSetHasChangedAfterLayout && 2122 predictiveItemAnimationsEnabled(); 2123 } 2124 2125 /** 2126 * Wrapper around layoutChildren() that handles animating changes caused by layout. 2127 * Animations work on the assumption that there are five different kinds of items 2128 * in play: 2129 * PERSISTENT: items are visible before and after layout 2130 * REMOVED: items were visible before layout and were removed by the app 2131 * ADDED: items did not exist before layout and were added by the app 2132 * DISAPPEARING: items exist in the data set before/after, but changed from 2133 * visible to non-visible in the process of layout (they were moved off 2134 * screen as a side-effect of other changes) 2135 * APPEARING: items exist in the data set before/after, but changed from 2136 * non-visible to visible in the process of layout (they were moved on 2137 * screen as a side-effect of other changes) 2138 * The overall approach figures out what items exist before/after layout and 2139 * infers one of the five above states for each of the items. Then the animations 2140 * are set up accordingly: 2141 * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)}) 2142 * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)}) 2143 * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)}) 2144 * DISAPPEARING views are moved off screen 2145 * APPEARING views are moved on screen 2146 */ 2147 void dispatchLayout() { 2148 if (mAdapter == null) { 2149 Log.e(TAG, "No adapter attached; skipping layout"); 2150 return; 2151 } 2152 if (mLayout == null) { 2153 Log.e(TAG, "No layout manager attached; skipping layout"); 2154 return; 2155 } 2156 mDisappearingViewsInLayoutPass.clear(); 2157 eatRequestLayout(); 2158 mRunningLayoutOrScroll = true; 2159 2160 processAdapterUpdatesAndSetAnimationFlags(); 2161 2162 mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged 2163 && supportsChangeAnimations() ? new ArrayMap<Long, ViewHolder>() : null; 2164 mItemsAddedOrRemoved = mItemsChanged = false; 2165 ArrayMap<View, Rect> appearingViewInitialBounds = null; 2166 mState.mInPreLayout = mState.mRunPredictiveAnimations; 2167 mState.mItemCount = mAdapter.getItemCount(); 2168 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 2169 2170 if (mState.mRunSimpleAnimations) { 2171 // Step 0: Find out where all non-removed items are, pre-layout 2172 mState.mPreLayoutHolderMap.clear(); 2173 mState.mPostLayoutHolderMap.clear(); 2174 int count = mChildHelper.getChildCount(); 2175 for (int i = 0; i < count; ++i) { 2176 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2177 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { 2178 continue; 2179 } 2180 final View view = holder.itemView; 2181 mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder, 2182 view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 2183 } 2184 } 2185 if (mState.mRunPredictiveAnimations) { 2186 // Step 1: run prelayout: This will use the old positions of items. The layout manager 2187 // is expected to layout everything, even removed items (though not to add removed 2188 // items back to the container). This gives the pre-layout position of APPEARING views 2189 // which come into existence as part of the real layout. 2190 2191 // Save old positions so that LayoutManager can run its mapping logic. 2192 saveOldPositions(); 2193 // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations. 2194 if (mState.mOldChangedHolders != null) { 2195 int count = mChildHelper.getChildCount(); 2196 for (int i = 0; i < count; ++i) { 2197 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2198 if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { 2199 long key = getChangedHolderKey(holder); 2200 mState.mOldChangedHolders.put(key, holder); 2201 mState.mPreLayoutHolderMap.remove(holder); 2202 } 2203 } 2204 } 2205 2206 final boolean didStructureChange = mState.mStructureChanged; 2207 mState.mStructureChanged = false; 2208 // temporarily disable flag because we are asking for previous layout 2209 mLayout.onLayoutChildren(mRecycler, mState); 2210 mState.mStructureChanged = didStructureChange; 2211 2212 appearingViewInitialBounds = new ArrayMap<View, Rect>(); 2213 for (int i = 0; i < mChildHelper.getChildCount(); ++i) { 2214 boolean found = false; 2215 View child = mChildHelper.getChildAt(i); 2216 if (getChildViewHolderInt(child).shouldIgnore()) { 2217 continue; 2218 } 2219 for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) { 2220 ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j); 2221 if (holder.itemView == child) { 2222 found = true; 2223 break; 2224 } 2225 } 2226 if (!found) { 2227 appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(), 2228 child.getRight(), child.getBottom())); 2229 } 2230 } 2231 // we don't process disappearing list because they may re-appear in post layout pass. 2232 clearOldPositions(); 2233 mAdapterHelper.consumePostponedUpdates(); 2234 } else { 2235 clearOldPositions(); 2236 // in case pre layout did run but we decided not to run predictive animations. 2237 mAdapterHelper.consumeUpdatesInOnePass(); 2238 if (mState.mOldChangedHolders != null) { 2239 int count = mChildHelper.getChildCount(); 2240 for (int i = 0; i < count; ++i) { 2241 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2242 if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { 2243 long key = getChangedHolderKey(holder); 2244 mState.mOldChangedHolders.put(key, holder); 2245 mState.mPreLayoutHolderMap.remove(holder); 2246 } 2247 } 2248 } 2249 } 2250 mState.mItemCount = mAdapter.getItemCount(); 2251 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; 2252 2253 // Step 2: Run layout 2254 mState.mInPreLayout = false; 2255 mLayout.onLayoutChildren(mRecycler, mState); 2256 2257 mState.mStructureChanged = false; 2258 mPendingSavedState = null; 2259 2260 // onLayoutChildren may have caused client code to disable item animations; re-check 2261 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; 2262 2263 if (mState.mRunSimpleAnimations) { 2264 // Step 3: Find out where things are now, post-layout 2265 ArrayMap<Long, ViewHolder> newChangedHolders = mState.mOldChangedHolders != null ? 2266 new ArrayMap<Long, ViewHolder>() : null; 2267 int count = mChildHelper.getChildCount(); 2268 for (int i = 0; i < count; ++i) { 2269 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2270 if (holder.shouldIgnore()) { 2271 continue; 2272 } 2273 final View view = holder.itemView; 2274 long key = getChangedHolderKey(holder); 2275 if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) { 2276 newChangedHolders.put(key, holder); 2277 } else { 2278 mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder, 2279 view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); 2280 } 2281 } 2282 processDisappearingList(appearingViewInitialBounds); 2283 // Step 4: Animate DISAPPEARING and REMOVED items 2284 int preLayoutCount = mState.mPreLayoutHolderMap.size(); 2285 for (int i = preLayoutCount - 1; i >= 0; i--) { 2286 ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i); 2287 if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) { 2288 ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i); 2289 mState.mPreLayoutHolderMap.removeAt(i); 2290 2291 View disappearingItemView = disappearingItem.holder.itemView; 2292 mRecycler.unscrapView(disappearingItem.holder); 2293 animateDisappearance(disappearingItem); 2294 } 2295 } 2296 // Step 5: Animate APPEARING and ADDED items 2297 int postLayoutCount = mState.mPostLayoutHolderMap.size(); 2298 if (postLayoutCount > 0) { 2299 for (int i = postLayoutCount - 1; i >= 0; i--) { 2300 ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i); 2301 ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i); 2302 if ((mState.mPreLayoutHolderMap.isEmpty() || 2303 !mState.mPreLayoutHolderMap.containsKey(itemHolder))) { 2304 mState.mPostLayoutHolderMap.removeAt(i); 2305 Rect initialBounds = (appearingViewInitialBounds != null) ? 2306 appearingViewInitialBounds.get(itemHolder.itemView) : null; 2307 animateAppearance(itemHolder, initialBounds, 2308 info.left, info.top); 2309 } 2310 } 2311 } 2312 // Step 6: Animate PERSISTENT items 2313 count = mState.mPostLayoutHolderMap.size(); 2314 for (int i = 0; i < count; ++i) { 2315 ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i); 2316 ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i); 2317 ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder); 2318 if (preInfo != null && postInfo != null) { 2319 if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) { 2320 postHolder.setIsRecyclable(false); 2321 if (DEBUG) { 2322 Log.d(TAG, "PERSISTENT: " + postHolder + 2323 " with view " + postHolder.itemView); 2324 } 2325 if (mItemAnimator.animateMove(postHolder, 2326 preInfo.left, preInfo.top, postInfo.left, postInfo.top)) { 2327 postAnimationRunner(); 2328 } 2329 } 2330 } 2331 } 2332 // Step 7: Animate CHANGING items 2333 count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0; 2334 // traverse reverse in case view gets recycled while we are traversing the list. 2335 for (int i = count - 1; i >= 0; i--) { 2336 long key = mState.mOldChangedHolders.keyAt(i); 2337 ViewHolder oldHolder = mState.mOldChangedHolders.get(key); 2338 View oldView = oldHolder.itemView; 2339 if (oldHolder.shouldIgnore()) { 2340 continue; 2341 } 2342 // We probably don't need this check anymore since these views are removed from 2343 // the list if they are recycled. 2344 if (mRecycler.mChangedScrap != null && 2345 mRecycler.mChangedScrap.contains(oldHolder)) { 2346 animateChange(oldHolder, newChangedHolders.get(key)); 2347 } else if (DEBUG) { 2348 Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder); 2349 } 2350 } 2351 } 2352 resumeRequestLayout(false); 2353 mLayout.removeAndRecycleScrapInt(mRecycler); 2354 mState.mPreviousLayoutItemCount = mState.mItemCount; 2355 mDataSetHasChangedAfterLayout = false; 2356 mState.mRunSimpleAnimations = false; 2357 mState.mRunPredictiveAnimations = false; 2358 mRunningLayoutOrScroll = false; 2359 mLayout.mRequestedSimpleAnimations = false; 2360 if (mRecycler.mChangedScrap != null) { 2361 mRecycler.mChangedScrap.clear(); 2362 } 2363 mState.mOldChangedHolders = null; 2364 2365 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { 2366 dispatchOnScrolled(0, 0); 2367 } 2368 } 2369 2370 private void findMinMaxChildLayoutPositions(int[] into) { 2371 final int count = mChildHelper.getChildCount(); 2372 if (count == 0) { 2373 into[0] = 0; 2374 into[1] = 0; 2375 return; 2376 } 2377 int minPositionPreLayout = Integer.MAX_VALUE; 2378 int maxPositionPreLayout = Integer.MIN_VALUE; 2379 for (int i = 0; i < count; ++i) { 2380 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2381 if (holder.shouldIgnore()) { 2382 continue; 2383 } 2384 final int pos = holder.getLayoutPosition(); 2385 if (pos < minPositionPreLayout) { 2386 minPositionPreLayout = pos; 2387 } 2388 if (pos > maxPositionPreLayout) { 2389 maxPositionPreLayout = pos; 2390 } 2391 } 2392 into[0] = minPositionPreLayout; 2393 into[1] = maxPositionPreLayout; 2394 } 2395 2396 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { 2397 int count = mChildHelper.getChildCount(); 2398 if (count == 0) { 2399 return minPositionPreLayout != 0 || maxPositionPreLayout != 0; 2400 } 2401 for (int i = 0; i < count; ++i) { 2402 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2403 if (holder.shouldIgnore()) { 2404 continue; 2405 } 2406 final int pos = holder.getLayoutPosition(); 2407 if (pos < minPositionPreLayout || pos > maxPositionPreLayout) { 2408 return true; 2409 } 2410 } 2411 return false; 2412 } 2413 2414 @Override 2415 protected void removeDetachedView(View child, boolean animate) { 2416 ViewHolder vh = getChildViewHolderInt(child); 2417 if (vh != null) { 2418 if (vh.isTmpDetached()) { 2419 vh.clearTmpDetachFlag(); 2420 } else if (!vh.shouldIgnore()) { 2421 throw new IllegalArgumentException("Called removeDetachedView with a view which" 2422 + " is not flagged as tmp detached." + vh); 2423 } 2424 } 2425 dispatchChildDetached(child); 2426 super.removeDetachedView(child, animate); 2427 } 2428 2429 /** 2430 * Returns a unique key to be used while handling change animations. 2431 * It might be child's position or stable id depending on the adapter type. 2432 */ 2433 long getChangedHolderKey(ViewHolder holder) { 2434 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; 2435 } 2436 2437 /** 2438 * A LayoutManager may want to layout a view just to animate disappearance. 2439 * This method handles those views and triggers remove animation on them. 2440 */ 2441 private void processDisappearingList(ArrayMap<View, Rect> appearingViews) { 2442 final int count = mDisappearingViewsInLayoutPass.size(); 2443 for (int i = 0; i < count; i ++) { 2444 View view = mDisappearingViewsInLayoutPass.get(i); 2445 ViewHolder vh = getChildViewHolderInt(view); 2446 final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh); 2447 if (!mState.isPreLayout()) { 2448 mState.mPostLayoutHolderMap.remove(vh); 2449 } 2450 if (appearingViews.remove(view) != null) { 2451 mLayout.removeAndRecycleView(view, mRecycler); 2452 continue; 2453 } 2454 if (info != null) { 2455 animateDisappearance(info); 2456 } else { 2457 // let it disappear from the position it becomes visible 2458 animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(), 2459 view.getRight(), view.getBottom())); 2460 } 2461 } 2462 mDisappearingViewsInLayoutPass.clear(); 2463 } 2464 2465 private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft, 2466 int afterTop) { 2467 View newItemView = itemHolder.itemView; 2468 if (beforeBounds != null && 2469 (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) { 2470 // slide items in if before/after locations differ 2471 itemHolder.setIsRecyclable(false); 2472 if (DEBUG) { 2473 Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView); 2474 } 2475 if (mItemAnimator.animateMove(itemHolder, 2476 beforeBounds.left, beforeBounds.top, 2477 afterLeft, afterTop)) { 2478 postAnimationRunner(); 2479 } 2480 } else { 2481 if (DEBUG) { 2482 Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView); 2483 } 2484 itemHolder.setIsRecyclable(false); 2485 if (mItemAnimator.animateAdd(itemHolder)) { 2486 postAnimationRunner(); 2487 } 2488 } 2489 } 2490 2491 private void animateDisappearance(ItemHolderInfo disappearingItem) { 2492 View disappearingItemView = disappearingItem.holder.itemView; 2493 addAnimatingView(disappearingItem.holder); 2494 int oldLeft = disappearingItem.left; 2495 int oldTop = disappearingItem.top; 2496 int newLeft = disappearingItemView.getLeft(); 2497 int newTop = disappearingItemView.getTop(); 2498 if (oldLeft != newLeft || oldTop != newTop) { 2499 disappearingItem.holder.setIsRecyclable(false); 2500 disappearingItemView.layout(newLeft, newTop, 2501 newLeft + disappearingItemView.getWidth(), 2502 newTop + disappearingItemView.getHeight()); 2503 if (DEBUG) { 2504 Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder + 2505 " with view " + disappearingItemView); 2506 } 2507 if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop, 2508 newLeft, newTop)) { 2509 postAnimationRunner(); 2510 } 2511 } else { 2512 if (DEBUG) { 2513 Log.d(TAG, "REMOVED: " + disappearingItem.holder + 2514 " with view " + disappearingItemView); 2515 } 2516 disappearingItem.holder.setIsRecyclable(false); 2517 if (mItemAnimator.animateRemove(disappearingItem.holder)) { 2518 postAnimationRunner(); 2519 } 2520 } 2521 } 2522 2523 private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) { 2524 oldHolder.setIsRecyclable(false); 2525 addAnimatingView(oldHolder); 2526 oldHolder.mShadowedHolder = newHolder; 2527 mRecycler.unscrapView(oldHolder); 2528 if (DEBUG) { 2529 Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); 2530 } 2531 final int fromLeft = oldHolder.itemView.getLeft(); 2532 final int fromTop = oldHolder.itemView.getTop(); 2533 final int toLeft, toTop; 2534 if (newHolder == null || newHolder.shouldIgnore()) { 2535 toLeft = fromLeft; 2536 toTop = fromTop; 2537 } else { 2538 toLeft = newHolder.itemView.getLeft(); 2539 toTop = newHolder.itemView.getTop(); 2540 newHolder.setIsRecyclable(false); 2541 newHolder.mShadowingHolder = oldHolder; 2542 } 2543 if(mItemAnimator.animateChange(oldHolder, newHolder, 2544 fromLeft, fromTop, toLeft, toTop)) { 2545 postAnimationRunner(); 2546 } 2547 } 2548 2549 @Override 2550 protected void onLayout(boolean changed, int l, int t, int r, int b) { 2551 eatRequestLayout(); 2552 dispatchLayout(); 2553 resumeRequestLayout(false); 2554 mFirstLayoutComplete = true; 2555 } 2556 2557 @Override 2558 public void requestLayout() { 2559 if (!mEatRequestLayout) { 2560 super.requestLayout(); 2561 } else { 2562 mLayoutRequestEaten = true; 2563 } 2564 } 2565 2566 void markItemDecorInsetsDirty() { 2567 final int childCount = mChildHelper.getUnfilteredChildCount(); 2568 for (int i = 0; i < childCount; i++) { 2569 final View child = mChildHelper.getUnfilteredChildAt(i); 2570 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 2571 } 2572 mRecycler.markItemDecorInsetsDirty(); 2573 } 2574 2575 @Override 2576 public void draw(Canvas c) { 2577 super.draw(c); 2578 2579 final int count = mItemDecorations.size(); 2580 for (int i = 0; i < count; i++) { 2581 mItemDecorations.get(i).onDrawOver(c, this, mState); 2582 } 2583 // TODO If padding is not 0 and chilChildrenToPadding is false, to draw glows properly, we 2584 // need find children closest to edges. Not sure if it is worth the effort. 2585 boolean needsInvalidate = false; 2586 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 2587 final int restore = c.save(); 2588 final int padding = mClipToPadding ? getPaddingBottom() : 0; 2589 c.rotate(270); 2590 c.translate(-getHeight() + padding, 0); 2591 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 2592 c.restoreToCount(restore); 2593 } 2594 if (mTopGlow != null && !mTopGlow.isFinished()) { 2595 final int restore = c.save(); 2596 if (mClipToPadding) { 2597 c.translate(getPaddingLeft(), getPaddingTop()); 2598 } 2599 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 2600 c.restoreToCount(restore); 2601 } 2602 if (mRightGlow != null && !mRightGlow.isFinished()) { 2603 final int restore = c.save(); 2604 final int width = getWidth(); 2605 final int padding = mClipToPadding ? getPaddingTop() : 0; 2606 c.rotate(90); 2607 c.translate(-padding, -width); 2608 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 2609 c.restoreToCount(restore); 2610 } 2611 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 2612 final int restore = c.save(); 2613 c.rotate(180); 2614 if (mClipToPadding) { 2615 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); 2616 } else { 2617 c.translate(-getWidth(), -getHeight()); 2618 } 2619 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 2620 c.restoreToCount(restore); 2621 } 2622 2623 // If some views are animating, ItemDecorators are likely to move/change with them. 2624 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's 2625 // display lists are not invalidated. 2626 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 && 2627 mItemAnimator.isRunning()) { 2628 needsInvalidate = true; 2629 } 2630 2631 if (needsInvalidate) { 2632 ViewCompat.postInvalidateOnAnimation(this); 2633 } 2634 } 2635 2636 @Override 2637 public void onDraw(Canvas c) { 2638 super.onDraw(c); 2639 2640 final int count = mItemDecorations.size(); 2641 for (int i = 0; i < count; i++) { 2642 mItemDecorations.get(i).onDraw(c, this, mState); 2643 } 2644 } 2645 2646 @Override 2647 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2648 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 2649 } 2650 2651 @Override 2652 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 2653 if (mLayout == null) { 2654 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2655 } 2656 return mLayout.generateDefaultLayoutParams(); 2657 } 2658 2659 @Override 2660 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 2661 if (mLayout == null) { 2662 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2663 } 2664 return mLayout.generateLayoutParams(getContext(), attrs); 2665 } 2666 2667 @Override 2668 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2669 if (mLayout == null) { 2670 throw new IllegalStateException("RecyclerView has no LayoutManager"); 2671 } 2672 return mLayout.generateLayoutParams(p); 2673 } 2674 2675 void saveOldPositions() { 2676 final int childCount = mChildHelper.getUnfilteredChildCount(); 2677 for (int i = 0; i < childCount; i++) { 2678 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2679 if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { 2680 throw new IllegalStateException("view holder cannot have position -1 unless it" 2681 + " is removed"); 2682 } 2683 if (!holder.shouldIgnore()) { 2684 holder.saveOldPosition(); 2685 } 2686 } 2687 } 2688 2689 void clearOldPositions() { 2690 final int childCount = mChildHelper.getUnfilteredChildCount(); 2691 for (int i = 0; i < childCount; i++) { 2692 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2693 if (!holder.shouldIgnore()) { 2694 holder.clearOldPosition(); 2695 } 2696 } 2697 mRecycler.clearOldPositions(); 2698 } 2699 2700 void offsetPositionRecordsForMove(int from, int to) { 2701 final int childCount = mChildHelper.getUnfilteredChildCount(); 2702 final int start, end, inBetweenOffset; 2703 if (from < to) { 2704 start = from; 2705 end = to; 2706 inBetweenOffset = -1; 2707 } else { 2708 start = to; 2709 end = from; 2710 inBetweenOffset = 1; 2711 } 2712 2713 for (int i = 0; i < childCount; i++) { 2714 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2715 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 2716 continue; 2717 } 2718 if (DEBUG) { 2719 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " + 2720 holder); 2721 } 2722 if (holder.mPosition == from) { 2723 holder.offsetPosition(to - from, false); 2724 } else { 2725 holder.offsetPosition(inBetweenOffset, false); 2726 } 2727 2728 mState.mStructureChanged = true; 2729 } 2730 mRecycler.offsetPositionRecordsForMove(from, to); 2731 requestLayout(); 2732 } 2733 2734 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 2735 final int childCount = mChildHelper.getUnfilteredChildCount(); 2736 for (int i = 0; i < childCount; i++) { 2737 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2738 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { 2739 if (DEBUG) { 2740 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + 2741 holder + " now at position " + (holder.mPosition + itemCount)); 2742 } 2743 holder.offsetPosition(itemCount, false); 2744 mState.mStructureChanged = true; 2745 } 2746 } 2747 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 2748 requestLayout(); 2749 } 2750 2751 void offsetPositionRecordsForRemove(int positionStart, int itemCount, 2752 boolean applyToPreLayout) { 2753 final int positionEnd = positionStart + itemCount; 2754 final int childCount = mChildHelper.getUnfilteredChildCount(); 2755 for (int i = 0; i < childCount; i++) { 2756 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2757 if (holder != null && !holder.shouldIgnore()) { 2758 if (holder.mPosition >= positionEnd) { 2759 if (DEBUG) { 2760 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 2761 " holder " + holder + " now at position " + 2762 (holder.mPosition - itemCount)); 2763 } 2764 holder.offsetPosition(-itemCount, applyToPreLayout); 2765 mState.mStructureChanged = true; 2766 } else if (holder.mPosition >= positionStart) { 2767 if (DEBUG) { 2768 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 2769 " holder " + holder + " now REMOVED"); 2770 } 2771 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, 2772 applyToPreLayout); 2773 mState.mStructureChanged = true; 2774 } 2775 } 2776 } 2777 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); 2778 requestLayout(); 2779 } 2780 2781 /** 2782 * Rebind existing views for the given range, or create as needed. 2783 * 2784 * @param positionStart Adapter position to start at 2785 * @param itemCount Number of views that must explicitly be rebound 2786 */ 2787 void viewRangeUpdate(int positionStart, int itemCount) { 2788 final int childCount = mChildHelper.getUnfilteredChildCount(); 2789 final int positionEnd = positionStart + itemCount; 2790 2791 for (int i = 0; i < childCount; i++) { 2792 final View child = mChildHelper.getUnfilteredChildAt(i); 2793 final ViewHolder holder = getChildViewHolderInt(child); 2794 if (holder == null || holder.shouldIgnore()) { 2795 continue; 2796 } 2797 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 2798 // We re-bind these view holders after pre-processing is complete so that 2799 // ViewHolders have their final positions assigned. 2800 holder.addFlags(ViewHolder.FLAG_UPDATE); 2801 if (supportsChangeAnimations()) { 2802 holder.addFlags(ViewHolder.FLAG_CHANGED); 2803 } 2804 // lp cannot be null since we get ViewHolder from it. 2805 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 2806 } 2807 } 2808 mRecycler.viewRangeUpdate(positionStart, itemCount); 2809 } 2810 2811 void rebindUpdatedViewHolders() { 2812 final int childCount = mChildHelper.getChildCount(); 2813 for (int i = 0; i < childCount; i++) { 2814 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2815 // validate type is correct 2816 if (holder == null || holder.shouldIgnore()) { 2817 continue; 2818 } 2819 if (holder.isRemoved() || holder.isInvalid()) { 2820 requestLayout(); 2821 } else if (holder.needsUpdate()) { 2822 final int type = mAdapter.getItemViewType(holder.mPosition); 2823 if (holder.getItemViewType() == type) { 2824 // Binding an attached view will request a layout if needed. 2825 if (!holder.isChanged() || !supportsChangeAnimations()) { 2826 mAdapter.bindViewHolder(holder, holder.mPosition); 2827 } else { 2828 // Don't rebind changed holders if change animations are enabled. 2829 // We want the old contents for the animation and will get a new 2830 // holder for the new contents. 2831 requestLayout(); 2832 } 2833 } else { 2834 // binding to a new view will need re-layout anyways. We can as well trigger 2835 // it here so that it happens during layout 2836 requestLayout(); 2837 break; 2838 } 2839 } 2840 } 2841 } 2842 2843 private void setDataSetChangedAfterLayout() { 2844 if (mDataSetHasChangedAfterLayout) { 2845 return; 2846 } 2847 mDataSetHasChangedAfterLayout = true; 2848 final int childCount = mChildHelper.getUnfilteredChildCount(); 2849 for (int i = 0; i < childCount; i++) { 2850 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2851 if (holder != null && !holder.shouldIgnore()) { 2852 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 2853 } 2854 } 2855 mRecycler.setAdapterPositionsAsUnknown(); 2856 } 2857 2858 /** 2859 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 2860 * data change event. 2861 */ 2862 void markKnownViewsInvalid() { 2863 final int childCount = mChildHelper.getUnfilteredChildCount(); 2864 for (int i = 0; i < childCount; i++) { 2865 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 2866 if (holder != null && !holder.shouldIgnore()) { 2867 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 2868 } 2869 } 2870 markItemDecorInsetsDirty(); 2871 mRecycler.markKnownViewsInvalid(); 2872 } 2873 2874 /** 2875 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method 2876 * will trigger a {@link #requestLayout()} call. 2877 */ 2878 public void invalidateItemDecorations() { 2879 if (mItemDecorations.size() == 0) { 2880 return; 2881 } 2882 if (mLayout != null) { 2883 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" 2884 + " or layout"); 2885 } 2886 markItemDecorInsetsDirty(); 2887 requestLayout(); 2888 } 2889 2890 /** 2891 * Retrieve the {@link ViewHolder} for the given child view. 2892 * 2893 * @param child Child of this RecyclerView to query for its ViewHolder 2894 * @return The child view's ViewHolder 2895 */ 2896 public ViewHolder getChildViewHolder(View child) { 2897 final ViewParent parent = child.getParent(); 2898 if (parent != null && parent != this) { 2899 throw new IllegalArgumentException("View " + child + " is not a direct child of " + 2900 this); 2901 } 2902 return getChildViewHolderInt(child); 2903 } 2904 2905 static ViewHolder getChildViewHolderInt(View child) { 2906 if (child == null) { 2907 return null; 2908 } 2909 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 2910 } 2911 2912 /** 2913 * @deprecated use {@link #getChildAdapterPosition(View)} or 2914 * {@link #getChildLayoutPosition(View)}. 2915 */ 2916 @Deprecated 2917 public int getChildPosition(View child) { 2918 return getChildAdapterPosition(child); 2919 } 2920 2921 /** 2922 * Return the adapter position that the given child view corresponds to. 2923 * 2924 * @param child Child View to query 2925 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 2926 */ 2927 public int getChildAdapterPosition(View child) { 2928 final ViewHolder holder = getChildViewHolderInt(child); 2929 return holder != null ? holder.getAdapterPosition() : NO_POSITION; 2930 } 2931 2932 /** 2933 * Return the adapter position of the given child view as of the latest completed layout pass. 2934 * <p> 2935 * This position may not be equal to Item's adapter position if there are pending changes 2936 * in the adapter which have not been reflected to the layout yet. 2937 * 2938 * @param child Child View to query 2939 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if 2940 * the View is representing a removed item. 2941 */ 2942 public int getChildLayoutPosition(View child) { 2943 final ViewHolder holder = getChildViewHolderInt(child); 2944 return holder != null ? holder.getLayoutPosition() : NO_POSITION; 2945 } 2946 2947 /** 2948 * Return the stable item id that the given child view corresponds to. 2949 * 2950 * @param child Child View to query 2951 * @return Item id corresponding to the given view or {@link #NO_ID} 2952 */ 2953 public long getChildItemId(View child) { 2954 if (mAdapter == null || !mAdapter.hasStableIds()) { 2955 return NO_ID; 2956 } 2957 final ViewHolder holder = getChildViewHolderInt(child); 2958 return holder != null ? holder.getItemId() : NO_ID; 2959 } 2960 2961 /** 2962 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or 2963 * {@link #findViewHolderForAdapterPosition(int)} 2964 */ 2965 @Deprecated 2966 public ViewHolder findViewHolderForPosition(int position) { 2967 return findViewHolderForPosition(position, false); 2968 } 2969 2970 /** 2971 * Return the ViewHolder for the item in the given position of the data set as of the latest 2972 * layout pass. 2973 * <p> 2974 * This method checks only the children of RecyclerView. If the item at the given 2975 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 2976 * <p> 2977 * Note that when Adapter contents change, ViewHolder positions are not updated until the 2978 * next layout calculation. If there are pending adapter updates, the return value of this 2979 * method may not match your adapter contents. You can use 2980 * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. 2981 * 2982 * @param position The position of the item in the data set of the adapter 2983 * @return The ViewHolder at <code>position</code> or null if there is no such item 2984 */ 2985 public ViewHolder findViewHolderForLayoutPosition(int position) { 2986 return findViewHolderForPosition(position, false); 2987 } 2988 2989 /** 2990 * Return the ViewHolder for the item in the given position of the data set. Unlike 2991 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending 2992 * adapter changes that may not be reflected to the layout yet. On the other hand, if 2993 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been 2994 * calculated yet, this method will return <code>null</code> since the new positions of views 2995 * are unknown until the layout is calculated. 2996 * <p> 2997 * This method checks only the children of RecyclerView. If the item at the given 2998 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 2999 * 3000 * @param position The position of the item in the data set of the adapter 3001 * @return The ViewHolder at <code>position</code> or null if there is no such item 3002 */ 3003 public ViewHolder findViewHolderForAdapterPosition(int position) { 3004 if (mDataSetHasChangedAfterLayout) { 3005 return null; 3006 } 3007 final int childCount = mChildHelper.getUnfilteredChildCount(); 3008 for (int i = 0; i < childCount; i++) { 3009 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 3010 if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) { 3011 return holder; 3012 } 3013 } 3014 return null; 3015 } 3016 3017 ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { 3018 final int childCount = mChildHelper.getUnfilteredChildCount(); 3019 for (int i = 0; i < childCount; i++) { 3020 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 3021 if (holder != null && !holder.isRemoved()) { 3022 if (checkNewPosition) { 3023 if (holder.mPosition == position) { 3024 return holder; 3025 } 3026 } else if (holder.getLayoutPosition() == position) { 3027 return holder; 3028 } 3029 } 3030 } 3031 // This method should not query cached views. It creates a problem during adapter updates 3032 // when we are dealing with already laid out views. Also, for the public method, it is more 3033 // reasonable to return null if position is not laid out. 3034 return null; 3035 } 3036 3037 /** 3038 * Return the ViewHolder for the item with the given id. The RecyclerView must 3039 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 3040 * return a non-null value. 3041 * <p> 3042 * This method checks only the children of RecyclerView. If the item with the given 3043 * <code>id</code> is not laid out, it <em>will not</em> create a new one. 3044 * 3045 * @param id The id for the requested item 3046 * @return The ViewHolder with the given <code>id</code> or null if there is no such item 3047 */ 3048 public ViewHolder findViewHolderForItemId(long id) { 3049 final int childCount = mChildHelper.getUnfilteredChildCount(); 3050 for (int i = 0; i < childCount; i++) { 3051 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 3052 if (holder != null && holder.getItemId() == id) { 3053 return holder; 3054 } 3055 } 3056 // this method should not query cached views. They are not children so they 3057 // should not be returned in this public method 3058 return null; 3059 } 3060 3061 /** 3062 * Find the topmost view under the given point. 3063 * 3064 * @param x Horizontal position in pixels to search 3065 * @param y Vertical position in pixels to search 3066 * @return The child view under (x, y) or null if no matching child is found 3067 */ 3068 public View findChildViewUnder(float x, float y) { 3069 final int count = mChildHelper.getChildCount(); 3070 for (int i = count - 1; i >= 0; i--) { 3071 final View child = mChildHelper.getChildAt(i); 3072 final float translationX = ViewCompat.getTranslationX(child); 3073 final float translationY = ViewCompat.getTranslationY(child); 3074 if (x >= child.getLeft() + translationX && 3075 x <= child.getRight() + translationX && 3076 y >= child.getTop() + translationY && 3077 y <= child.getBottom() + translationY) { 3078 return child; 3079 } 3080 } 3081 return null; 3082 } 3083 3084 /** 3085 * Offset the bounds of all child views by <code>dy</code> pixels. 3086 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 3087 * 3088 * @param dy Vertical pixel offset to apply to the bounds of all child views 3089 */ 3090 public void offsetChildrenVertical(int dy) { 3091 final int childCount = mChildHelper.getChildCount(); 3092 for (int i = 0; i < childCount; i++) { 3093 mChildHelper.getChildAt(i).offsetTopAndBottom(dy); 3094 } 3095 } 3096 3097 /** 3098 * Called when an item view is attached to this RecyclerView. 3099 * 3100 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 3101 * of child views as they become attached. This will be called before a 3102 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 3103 * changes.</p> 3104 * 3105 * @param child Child view that is now attached to this RecyclerView and its associated window 3106 */ 3107 public void onChildAttachedToWindow(View child) { 3108 } 3109 3110 /** 3111 * Called when an item view is detached from this RecyclerView. 3112 * 3113 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 3114 * of child views as they become detached. This will be called as a 3115 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 3116 * 3117 * @param child Child view that is now detached from this RecyclerView and its associated window 3118 */ 3119 public void onChildDetachedFromWindow(View child) { 3120 } 3121 3122 /** 3123 * Offset the bounds of all child views by <code>dx</code> pixels. 3124 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 3125 * 3126 * @param dx Horizontal pixel offset to apply to the bounds of all child views 3127 */ 3128 public void offsetChildrenHorizontal(int dx) { 3129 final int childCount = mChildHelper.getChildCount(); 3130 for (int i = 0; i < childCount; i++) { 3131 mChildHelper.getChildAt(i).offsetLeftAndRight(dx); 3132 } 3133 } 3134 3135 Rect getItemDecorInsetsForChild(View child) { 3136 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 3137 if (!lp.mInsetsDirty) { 3138 return lp.mDecorInsets; 3139 } 3140 3141 final Rect insets = lp.mDecorInsets; 3142 insets.set(0, 0, 0, 0); 3143 final int decorCount = mItemDecorations.size(); 3144 for (int i = 0; i < decorCount; i++) { 3145 mTempRect.set(0, 0, 0, 0); 3146 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 3147 insets.left += mTempRect.left; 3148 insets.top += mTempRect.top; 3149 insets.right += mTempRect.right; 3150 insets.bottom += mTempRect.bottom; 3151 } 3152 lp.mInsetsDirty = false; 3153 return insets; 3154 } 3155 3156 /** 3157 * Called when the scroll position of this RecyclerView changes. Subclasses should use 3158 * this method to respond to scrolling within the adapter's data set instead of an explicit 3159 * listener. 3160 * 3161 * <p>This method will always be invoked before listeners. If a subclass needs to perform 3162 * any additional upkeep or bookkeeping after scrolling but before listeners run, 3163 * this is a good place to do so.</p> 3164 * 3165 * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives 3166 * the distance scrolled in either direction within the adapter's data set instead of absolute 3167 * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from 3168 * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive 3169 * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which 3170 * do not correspond to the data set scroll position. However, some subclasses may choose 3171 * to use these fields as special offsets.</p> 3172 * 3173 * @param dx horizontal distance scrolled in pixels 3174 * @param dy vertical distance scrolled in pixels 3175 */ 3176 public void onScrolled(int dx, int dy) { 3177 // Do nothing 3178 } 3179 3180 void dispatchOnScrolled(int hresult, int vresult) { 3181 // Pass the current scrollX/scrollY values; no actual change in these properties occurred 3182 // but some general-purpose code may choose to respond to changes this way. 3183 final int scrollX = getScrollX(); 3184 final int scrollY = getScrollY(); 3185 onScrollChanged(scrollX, scrollY, scrollX, scrollY); 3186 3187 // Pass the real deltas to onScrolled, the RecyclerView-specific method. 3188 onScrolled(hresult, vresult); 3189 3190 // Invoke listeners last. Subclassed view methods always handle the event first. 3191 // All internal state is consistent by the time listeners are invoked. 3192 if (mScrollListener != null) { 3193 mScrollListener.onScrolled(this, hresult, vresult); 3194 } 3195 if (mScrollListeners != null) { 3196 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 3197 mScrollListeners.get(i).onScrolled(this, hresult, vresult); 3198 } 3199 } 3200 } 3201 3202 /** 3203 * Called when the scroll state of this RecyclerView changes. Subclasses should use this 3204 * method to respond to state changes instead of an explicit listener. 3205 * 3206 * <p>This method will always be invoked before listeners, but after the LayoutManager 3207 * responds to the scroll state change.</p> 3208 * 3209 * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, 3210 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} 3211 */ 3212 public void onScrollStateChanged(int state) { 3213 // Do nothing 3214 } 3215 3216 void dispatchOnScrollStateChanged(int state) { 3217 // Let the LayoutManager go first; this allows it to bring any properties into 3218 // a consistent state before the RecyclerView subclass responds. 3219 if (mLayout != null) { 3220 mLayout.onScrollStateChanged(state); 3221 } 3222 3223 // Let the RecyclerView subclass handle this event next; any LayoutManager property 3224 // changes will be reflected by this time. 3225 onScrollStateChanged(state); 3226 3227 // Listeners go last. All other internal state is consistent by this point. 3228 if (mScrollListener != null) { 3229 mScrollListener.onScrollStateChanged(this, state); 3230 } 3231 if (mScrollListeners != null) { 3232 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 3233 mScrollListeners.get(i).onScrollStateChanged(this, state); 3234 } 3235 } 3236 } 3237 3238 /** 3239 * Returns whether there are pending adapter updates which are not yet applied to the layout. 3240 * <p> 3241 * If this method returns <code>true</code>, it means that what user is currently seeing may not 3242 * reflect them adapter contents (depending on what has changed). 3243 * You may use this information to defer or cancel some operations. 3244 * <p> 3245 * This method returns true if RecyclerView has not yet calculated the first layout after it is 3246 * attached to the Window or the Adapter has been replaced. 3247 * 3248 * @return True if there are some adapter updates which are not yet reflected to layout or false 3249 * if layout is up to date. 3250 */ 3251 public boolean hasPendingAdapterUpdates() { 3252 return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout 3253 || mAdapterHelper.hasPendingUpdates(); 3254 } 3255 3256 private class ViewFlinger implements Runnable { 3257 private int mLastFlingX; 3258 private int mLastFlingY; 3259 private ScrollerCompat mScroller; 3260 private Interpolator mInterpolator = sQuinticInterpolator; 3261 3262 3263 // When set to true, postOnAnimation callbacks are delayed until the run method completes 3264 private boolean mEatRunOnAnimationRequest = false; 3265 3266 // Tracks if postAnimationCallback should be re-attached when it is done 3267 private boolean mReSchedulePostAnimationCallback = false; 3268 3269 public ViewFlinger() { 3270 mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator); 3271 } 3272 3273 @Override 3274 public void run() { 3275 disableRunOnAnimationRequests(); 3276 consumePendingUpdateOperations(); 3277 // keep a local reference so that if it is changed during onAnimation method, it won't 3278 // cause unexpected behaviors 3279 final ScrollerCompat scroller = mScroller; 3280 final SmoothScroller smoothScroller = mLayout.mSmoothScroller; 3281 if (scroller.computeScrollOffset()) { 3282 final int x = scroller.getCurrX(); 3283 final int y = scroller.getCurrY(); 3284 final int dx = x - mLastFlingX; 3285 final int dy = y - mLastFlingY; 3286 int hresult = 0; 3287 int vresult = 0; 3288 mLastFlingX = x; 3289 mLastFlingY = y; 3290 int overscrollX = 0, overscrollY = 0; 3291 if (mAdapter != null) { 3292 eatRequestLayout(); 3293 mRunningLayoutOrScroll = true; 3294 if (dx != 0) { 3295 hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); 3296 overscrollX = dx - hresult; 3297 } 3298 if (dy != 0) { 3299 vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); 3300 overscrollY = dy - vresult; 3301 } 3302 if (supportsChangeAnimations()) { 3303 // Fix up shadow views used by changing animations 3304 int count = mChildHelper.getChildCount(); 3305 for (int i = 0; i < count; i++) { 3306 View view = mChildHelper.getChildAt(i); 3307 ViewHolder holder = getChildViewHolder(view); 3308 if (holder != null && holder.mShadowingHolder != null) { 3309 View shadowingView = holder.mShadowingHolder.itemView; 3310 int left = view.getLeft(); 3311 int top = view.getTop(); 3312 if (left != shadowingView.getLeft() || 3313 top != shadowingView.getTop()) { 3314 shadowingView.layout(left, top, 3315 left + shadowingView.getWidth(), 3316 top + shadowingView.getHeight()); 3317 } 3318 } 3319 } 3320 } 3321 3322 if (smoothScroller != null && !smoothScroller.isPendingInitialRun() && 3323 smoothScroller.isRunning()) { 3324 final int adapterSize = mState.getItemCount(); 3325 if (adapterSize == 0) { 3326 smoothScroller.stop(); 3327 } else if (smoothScroller.getTargetPosition() >= adapterSize) { 3328 smoothScroller.setTargetPosition(adapterSize - 1); 3329 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 3330 } else { 3331 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 3332 } 3333 } 3334 mRunningLayoutOrScroll = false; 3335 resumeRequestLayout(false); 3336 } 3337 if (!mItemDecorations.isEmpty()) { 3338 invalidate(); 3339 } 3340 if (ViewCompat.getOverScrollMode(RecyclerView.this) != 3341 ViewCompat.OVER_SCROLL_NEVER) { 3342 considerReleasingGlowsOnScroll(dx, dy); 3343 } 3344 if (overscrollX != 0 || overscrollY != 0) { 3345 final int vel = (int) scroller.getCurrVelocity(); 3346 3347 int velX = 0; 3348 if (overscrollX != x) { 3349 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; 3350 } 3351 3352 int velY = 0; 3353 if (overscrollY != y) { 3354 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; 3355 } 3356 3357 if (ViewCompat.getOverScrollMode(RecyclerView.this) != 3358 ViewCompat.OVER_SCROLL_NEVER) { 3359 absorbGlows(velX, velY); 3360 } 3361 if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) && 3362 (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { 3363 scroller.abortAnimation(); 3364 } 3365 } 3366 if (hresult != 0 || vresult != 0) { 3367 dispatchOnScrolled(hresult, vresult); 3368 } 3369 3370 if (!awakenScrollBars()) { 3371 invalidate(); 3372 } 3373 3374 final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically() 3375 && vresult == dy; 3376 final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally() 3377 && hresult == dx; 3378 final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal 3379 || fullyConsumedVertical; 3380 3381 if (scroller.isFinished() || !fullyConsumedAny) { 3382 setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. 3383 } else { 3384 postOnAnimation(); 3385 } 3386 } 3387 // call this after the onAnimation is complete not to have inconsistent callbacks etc. 3388 if (smoothScroller != null && smoothScroller.isPendingInitialRun()) { 3389 smoothScroller.onAnimation(0, 0); 3390 } 3391 enableRunOnAnimationRequests(); 3392 } 3393 3394 private void disableRunOnAnimationRequests() { 3395 mReSchedulePostAnimationCallback = false; 3396 mEatRunOnAnimationRequest = true; 3397 } 3398 3399 private void enableRunOnAnimationRequests() { 3400 mEatRunOnAnimationRequest = false; 3401 if (mReSchedulePostAnimationCallback) { 3402 postOnAnimation(); 3403 } 3404 } 3405 3406 void postOnAnimation() { 3407 if (mEatRunOnAnimationRequest) { 3408 mReSchedulePostAnimationCallback = true; 3409 } else { 3410 removeCallbacks(this); 3411 ViewCompat.postOnAnimation(RecyclerView.this, this); 3412 } 3413 } 3414 3415 public void fling(int velocityX, int velocityY) { 3416 setScrollState(SCROLL_STATE_SETTLING); 3417 mLastFlingX = mLastFlingY = 0; 3418 mScroller.fling(0, 0, velocityX, velocityY, 3419 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 3420 postOnAnimation(); 3421 } 3422 3423 public void smoothScrollBy(int dx, int dy) { 3424 smoothScrollBy(dx, dy, 0, 0); 3425 } 3426 3427 public void smoothScrollBy(int dx, int dy, int vx, int vy) { 3428 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); 3429 } 3430 3431 private float distanceInfluenceForSnapDuration(float f) { 3432 f -= 0.5f; // center the values about 0. 3433 f *= 0.3f * Math.PI / 2.0f; 3434 return (float) Math.sin(f); 3435 } 3436 3437 private int computeScrollDuration(int dx, int dy, int vx, int vy) { 3438 final int absDx = Math.abs(dx); 3439 final int absDy = Math.abs(dy); 3440 final boolean horizontal = absDx > absDy; 3441 final int velocity = (int) Math.sqrt(vx * vx + vy * vy); 3442 final int delta = (int) Math.sqrt(dx * dx + dy * dy); 3443 final int containerSize = horizontal ? getWidth() : getHeight(); 3444 final int halfContainerSize = containerSize / 2; 3445 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); 3446 final float distance = halfContainerSize + halfContainerSize * 3447 distanceInfluenceForSnapDuration(distanceRatio); 3448 3449 final int duration; 3450 if (velocity > 0) { 3451 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 3452 } else { 3453 float absDelta = (float) (horizontal ? absDx : absDy); 3454 duration = (int) (((absDelta / containerSize) + 1) * 300); 3455 } 3456 return Math.min(duration, MAX_SCROLL_DURATION); 3457 } 3458 3459 public void smoothScrollBy(int dx, int dy, int duration) { 3460 smoothScrollBy(dx, dy, duration, sQuinticInterpolator); 3461 } 3462 3463 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { 3464 if (mInterpolator != interpolator) { 3465 mInterpolator = interpolator; 3466 mScroller = ScrollerCompat.create(getContext(), interpolator); 3467 } 3468 setScrollState(SCROLL_STATE_SETTLING); 3469 mLastFlingX = mLastFlingY = 0; 3470 mScroller.startScroll(0, 0, dx, dy, duration); 3471 postOnAnimation(); 3472 } 3473 3474 public void stop() { 3475 removeCallbacks(this); 3476 mScroller.abortAnimation(); 3477 } 3478 3479 } 3480 3481 private class RecyclerViewDataObserver extends AdapterDataObserver { 3482 @Override 3483 public void onChanged() { 3484 assertNotInLayoutOrScroll(null); 3485 if (mAdapter.hasStableIds()) { 3486 // TODO Determine what actually changed. 3487 // This is more important to implement now since this callback will disable all 3488 // animations because we cannot rely on positions. 3489 mState.mStructureChanged = true; 3490 setDataSetChangedAfterLayout(); 3491 } else { 3492 mState.mStructureChanged = true; 3493 setDataSetChangedAfterLayout(); 3494 } 3495 if (!mAdapterHelper.hasPendingUpdates()) { 3496 requestLayout(); 3497 } 3498 } 3499 3500 @Override 3501 public void onItemRangeChanged(int positionStart, int itemCount) { 3502 assertNotInLayoutOrScroll(null); 3503 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) { 3504 triggerUpdateProcessor(); 3505 } 3506 } 3507 3508 @Override 3509 public void onItemRangeInserted(int positionStart, int itemCount) { 3510 assertNotInLayoutOrScroll(null); 3511 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 3512 triggerUpdateProcessor(); 3513 } 3514 } 3515 3516 @Override 3517 public void onItemRangeRemoved(int positionStart, int itemCount) { 3518 assertNotInLayoutOrScroll(null); 3519 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 3520 triggerUpdateProcessor(); 3521 } 3522 } 3523 3524 @Override 3525 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 3526 assertNotInLayoutOrScroll(null); 3527 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 3528 triggerUpdateProcessor(); 3529 } 3530 } 3531 3532 void triggerUpdateProcessor() { 3533 if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { 3534 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); 3535 } else { 3536 mAdapterUpdateDuringMeasure = true; 3537 requestLayout(); 3538 } 3539 } 3540 } 3541 3542 /** 3543 * RecycledViewPool lets you share Views between multiple RecyclerViews. 3544 * <p> 3545 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool 3546 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. 3547 * <p> 3548 * RecyclerView automatically creates a pool for itself if you don't provide one. 3549 * 3550 */ 3551 public static class RecycledViewPool { 3552 private SparseArray<ArrayList<ViewHolder>> mScrap = 3553 new SparseArray<ArrayList<ViewHolder>>(); 3554 private SparseIntArray mMaxScrap = new SparseIntArray(); 3555 private int mAttachCount = 0; 3556 3557 private static final int DEFAULT_MAX_SCRAP = 5; 3558 3559 public void clear() { 3560 mScrap.clear(); 3561 } 3562 3563 public void setMaxRecycledViews(int viewType, int max) { 3564 mMaxScrap.put(viewType, max); 3565 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 3566 if (scrapHeap != null) { 3567 while (scrapHeap.size() > max) { 3568 scrapHeap.remove(scrapHeap.size() - 1); 3569 } 3570 } 3571 } 3572 3573 public ViewHolder getRecycledView(int viewType) { 3574 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 3575 if (scrapHeap != null && !scrapHeap.isEmpty()) { 3576 final int index = scrapHeap.size() - 1; 3577 final ViewHolder scrap = scrapHeap.get(index); 3578 scrapHeap.remove(index); 3579 return scrap; 3580 } 3581 return null; 3582 } 3583 3584 int size() { 3585 int count = 0; 3586 for (int i = 0; i < mScrap.size(); i ++) { 3587 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i); 3588 if (viewHolders != null) { 3589 count += viewHolders.size(); 3590 } 3591 } 3592 return count; 3593 } 3594 3595 public void putRecycledView(ViewHolder scrap) { 3596 final int viewType = scrap.getItemViewType(); 3597 final ArrayList scrapHeap = getScrapHeapForType(viewType); 3598 if (mMaxScrap.get(viewType) <= scrapHeap.size()) { 3599 return; 3600 } 3601 scrap.resetInternal(); 3602 scrapHeap.add(scrap); 3603 } 3604 3605 void attach(Adapter adapter) { 3606 mAttachCount++; 3607 } 3608 3609 void detach() { 3610 mAttachCount--; 3611 } 3612 3613 3614 /** 3615 * Detaches the old adapter and attaches the new one. 3616 * <p> 3617 * RecycledViewPool will clear its cache if it has only one adapter attached and the new 3618 * adapter uses a different ViewHolder than the oldAdapter. 3619 * 3620 * @param oldAdapter The previous adapter instance. Will be detached. 3621 * @param newAdapter The new adapter instance. Will be attached. 3622 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same 3623 * ViewHolder and view types. 3624 */ 3625 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 3626 boolean compatibleWithPrevious) { 3627 if (oldAdapter != null) { 3628 detach(); 3629 } 3630 if (!compatibleWithPrevious && mAttachCount == 0) { 3631 clear(); 3632 } 3633 if (newAdapter != null) { 3634 attach(newAdapter); 3635 } 3636 } 3637 3638 private ArrayList<ViewHolder> getScrapHeapForType(int viewType) { 3639 ArrayList<ViewHolder> scrap = mScrap.get(viewType); 3640 if (scrap == null) { 3641 scrap = new ArrayList<ViewHolder>(); 3642 mScrap.put(viewType, scrap); 3643 if (mMaxScrap.indexOfKey(viewType) < 0) { 3644 mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); 3645 } 3646 } 3647 return scrap; 3648 } 3649 } 3650 3651 /** 3652 * A Recycler is responsible for managing scrapped or detached item views for reuse. 3653 * 3654 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 3655 * that has been marked for removal or reuse.</p> 3656 * 3657 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 3658 * an adapter's data set representing the data at a given position or item ID. 3659 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 3660 * If not, the view can be quickly reused by the LayoutManager with no further work. 3661 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 3662 * may be repositioned by a LayoutManager without remeasurement.</p> 3663 */ 3664 public final class Recycler { 3665 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>(); 3666 private ArrayList<ViewHolder> mChangedScrap = null; 3667 3668 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 3669 3670 private final List<ViewHolder> 3671 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 3672 3673 private int mViewCacheMax = DEFAULT_CACHE_SIZE; 3674 3675 private RecycledViewPool mRecyclerPool; 3676 3677 private ViewCacheExtension mViewCacheExtension; 3678 3679 private static final int DEFAULT_CACHE_SIZE = 2; 3680 3681 /** 3682 * Clear scrap views out of this recycler. Detached views contained within a 3683 * recycled view pool will remain. 3684 */ 3685 public void clear() { 3686 mAttachedScrap.clear(); 3687 recycleAndClearCachedViews(); 3688 } 3689 3690 /** 3691 * Set the maximum number of detached, valid views we should retain for later use. 3692 * 3693 * @param viewCount Number of views to keep before sending views to the shared pool 3694 */ 3695 public void setViewCacheSize(int viewCount) { 3696 mViewCacheMax = viewCount; 3697 // first, try the views that can be recycled 3698 for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) { 3699 recycleCachedViewAt(i); 3700 } 3701 } 3702 3703 /** 3704 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. 3705 * 3706 * @return List of ViewHolders in the scrap list. 3707 */ 3708 public List<ViewHolder> getScrapList() { 3709 return mUnmodifiableAttachedScrap; 3710 } 3711 3712 /** 3713 * Helper method for getViewForPosition. 3714 * <p> 3715 * Checks whether a given view holder can be used for the provided position. 3716 * 3717 * @param holder ViewHolder 3718 * @return true if ViewHolder matches the provided position, false otherwise 3719 */ 3720 boolean validateViewHolderForOffsetPosition(ViewHolder holder) { 3721 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore 3722 // if it is not removed, verify the type and id. 3723 if (holder.isRemoved()) { 3724 return true; 3725 } 3726 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { 3727 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " 3728 + "adapter position" + holder); 3729 } 3730 if (!mState.isPreLayout()) { 3731 // don't check type if it is pre-layout. 3732 final int type = mAdapter.getItemViewType(holder.mPosition); 3733 if (type != holder.getItemViewType()) { 3734 return false; 3735 } 3736 } 3737 if (mAdapter.hasStableIds()) { 3738 return holder.getItemId() == mAdapter.getItemId(holder.mPosition); 3739 } 3740 return true; 3741 } 3742 3743 /** 3744 * Binds the given View to the position. The View can be a View previously retrieved via 3745 * {@link #getViewForPosition(int)} or created by 3746 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. 3747 * <p> 3748 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} 3749 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who 3750 * wants to handle its own recycling logic. 3751 * <p> 3752 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so 3753 * you don't need to call this method unless you want to bind this View to another position. 3754 * 3755 * @param view The view to update. 3756 * @param position The position of the item to bind to this View. 3757 */ 3758 public void bindViewToPosition(View view, int position) { 3759 ViewHolder holder = getChildViewHolderInt(view); 3760 if (holder == null) { 3761 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" 3762 + " pass arbitrary views to this method, they should be created by the " 3763 + "Adapter"); 3764 } 3765 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3766 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 3767 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 3768 + "position " + position + "(offset:" + offsetPosition + ")." 3769 + "state:" + mState.getItemCount()); 3770 } 3771 holder.mOwnerRecyclerView = RecyclerView.this; 3772 mAdapter.bindViewHolder(holder, offsetPosition); 3773 attachAccessibilityDelegate(view); 3774 if (mState.isPreLayout()) { 3775 holder.mPreLayoutPosition = position; 3776 } 3777 3778 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 3779 final LayoutParams rvLayoutParams; 3780 if (lp == null) { 3781 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 3782 holder.itemView.setLayoutParams(rvLayoutParams); 3783 } else if (!checkLayoutParams(lp)) { 3784 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 3785 holder.itemView.setLayoutParams(rvLayoutParams); 3786 } else { 3787 rvLayoutParams = (LayoutParams) lp; 3788 } 3789 3790 rvLayoutParams.mInsetsDirty = true; 3791 rvLayoutParams.mViewHolder = holder; 3792 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; 3793 } 3794 3795 /** 3796 * RecyclerView provides artificial position range (item count) in pre-layout state and 3797 * automatically maps these positions to {@link Adapter} positions when 3798 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. 3799 * <p> 3800 * Usually, LayoutManager does not need to worry about this. However, in some cases, your 3801 * LayoutManager may need to call some custom component with item positions in which 3802 * case you need the actual adapter position instead of the pre layout position. You 3803 * can use this method to convert a pre-layout position to adapter (post layout) position. 3804 * <p> 3805 * Note that if the provided position belongs to a deleted ViewHolder, this method will 3806 * return -1. 3807 * <p> 3808 * Calling this method in post-layout state returns the same value back. 3809 * 3810 * @param position The pre-layout position to convert. Must be greater or equal to 0 and 3811 * less than {@link State#getItemCount()}. 3812 */ 3813 public int convertPreLayoutPositionToPostLayout(int position) { 3814 if (position < 0 || position >= mState.getItemCount()) { 3815 throw new IndexOutOfBoundsException("invalid position " + position + ". State " 3816 + "item count is " + mState.getItemCount()); 3817 } 3818 if (!mState.isPreLayout()) { 3819 return position; 3820 } 3821 return mAdapterHelper.findPositionOffset(position); 3822 } 3823 3824 /** 3825 * Obtain a view initialized for the given position. 3826 * 3827 * This method should be used by {@link LayoutManager} implementations to obtain 3828 * views to represent data from an {@link Adapter}. 3829 * <p> 3830 * The Recycler may reuse a scrap or detached view from a shared pool if one is 3831 * available for the correct view type. If the adapter has not indicated that the 3832 * data at the given position has changed, the Recycler will attempt to hand back 3833 * a scrap view that was previously initialized for that data without rebinding. 3834 * 3835 * @param position Position to obtain a view for 3836 * @return A view representing the data at <code>position</code> from <code>adapter</code> 3837 */ 3838 public View getViewForPosition(int position) { 3839 return getViewForPosition(position, false); 3840 } 3841 3842 View getViewForPosition(int position, boolean dryRun) { 3843 if (position < 0 || position >= mState.getItemCount()) { 3844 throw new IndexOutOfBoundsException("Invalid item position " + position 3845 + "(" + position + "). Item count:" + mState.getItemCount()); 3846 } 3847 boolean fromScrap = false; 3848 ViewHolder holder = null; 3849 // 0) If there is a changed scrap, try to find from there 3850 if (mState.isPreLayout()) { 3851 holder = getChangedScrapViewForPosition(position); 3852 fromScrap = holder != null; 3853 } 3854 // 1) Find from scrap by position 3855 if (holder == null) { 3856 holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); 3857 if (holder != null) { 3858 if (!validateViewHolderForOffsetPosition(holder)) { 3859 // recycle this scrap 3860 if (!dryRun) { 3861 // we would like to recycle this but need to make sure it is not used by 3862 // animation logic etc. 3863 holder.addFlags(ViewHolder.FLAG_INVALID); 3864 if (holder.isScrap()) { 3865 removeDetachedView(holder.itemView, false); 3866 holder.unScrap(); 3867 } else if (holder.wasReturnedFromScrap()) { 3868 holder.clearReturnedFromScrapFlag(); 3869 } 3870 recycleViewHolderInternal(holder); 3871 } 3872 holder = null; 3873 } else { 3874 fromScrap = true; 3875 } 3876 } 3877 } 3878 if (holder == null) { 3879 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3880 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 3881 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 3882 + "position " + position + "(offset:" + offsetPosition + ")." 3883 + "state:" + mState.getItemCount()); 3884 } 3885 3886 final int type = mAdapter.getItemViewType(offsetPosition); 3887 // 2) Find from scrap via stable ids, if exists 3888 if (mAdapter.hasStableIds()) { 3889 holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); 3890 if (holder != null) { 3891 // update position 3892 holder.mPosition = offsetPosition; 3893 fromScrap = true; 3894 } 3895 } 3896 if (holder == null && mViewCacheExtension != null) { 3897 // We are NOT sending the offsetPosition because LayoutManager does not 3898 // know it. 3899 final View view = mViewCacheExtension 3900 .getViewForPositionAndType(this, position, type); 3901 if (view != null) { 3902 holder = getChildViewHolder(view); 3903 if (holder == null) { 3904 throw new IllegalArgumentException("getViewForPositionAndType returned" 3905 + " a view which does not have a ViewHolder"); 3906 } else if (holder.shouldIgnore()) { 3907 throw new IllegalArgumentException("getViewForPositionAndType returned" 3908 + " a view that is ignored. You must call stopIgnoring before" 3909 + " returning this view."); 3910 } 3911 } 3912 } 3913 if (holder == null) { // fallback to recycler 3914 // try recycler. 3915 // Head to the shared pool. 3916 if (DEBUG) { 3917 Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " 3918 + "pool"); 3919 } 3920 holder = getRecycledViewPool().getRecycledView(type); 3921 if (holder != null) { 3922 holder.resetInternal(); 3923 if (FORCE_INVALIDATE_DISPLAY_LIST) { 3924 invalidateDisplayListInt(holder); 3925 } 3926 } 3927 } 3928 if (holder == null) { 3929 holder = mAdapter.createViewHolder(RecyclerView.this, type); 3930 if (DEBUG) { 3931 Log.d(TAG, "getViewForPosition created new ViewHolder"); 3932 } 3933 } 3934 } 3935 boolean bound = false; 3936 if (mState.isPreLayout() && holder.isBound()) { 3937 // do not update unless we absolutely have to. 3938 holder.mPreLayoutPosition = position; 3939 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 3940 if (DEBUG && holder.isRemoved()) { 3941 throw new IllegalStateException("Removed holder should be bound and it should" 3942 + " come here only in pre-layout. Holder: " + holder); 3943 } 3944 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 3945 holder.mOwnerRecyclerView = RecyclerView.this; 3946 mAdapter.bindViewHolder(holder, offsetPosition); 3947 attachAccessibilityDelegate(holder.itemView); 3948 bound = true; 3949 if (mState.isPreLayout()) { 3950 holder.mPreLayoutPosition = position; 3951 } 3952 } 3953 3954 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 3955 final LayoutParams rvLayoutParams; 3956 if (lp == null) { 3957 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 3958 holder.itemView.setLayoutParams(rvLayoutParams); 3959 } else if (!checkLayoutParams(lp)) { 3960 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 3961 holder.itemView.setLayoutParams(rvLayoutParams); 3962 } else { 3963 rvLayoutParams = (LayoutParams) lp; 3964 } 3965 rvLayoutParams.mViewHolder = holder; 3966 rvLayoutParams.mPendingInvalidate = fromScrap && bound; 3967 return holder.itemView; 3968 } 3969 3970 private void attachAccessibilityDelegate(View itemView) { 3971 if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()) { 3972 if (ViewCompat.getImportantForAccessibility(itemView) == 3973 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 3974 ViewCompat.setImportantForAccessibility(itemView, 3975 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 3976 } 3977 if (!ViewCompat.hasAccessibilityDelegate(itemView)) { 3978 ViewCompat.setAccessibilityDelegate(itemView, 3979 mAccessibilityDelegate.getItemDelegate()); 3980 } 3981 } 3982 } 3983 3984 private void invalidateDisplayListInt(ViewHolder holder) { 3985 if (holder.itemView instanceof ViewGroup) { 3986 invalidateDisplayListInt((ViewGroup) holder.itemView, false); 3987 } 3988 } 3989 3990 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { 3991 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 3992 final View view = viewGroup.getChildAt(i); 3993 if (view instanceof ViewGroup) { 3994 invalidateDisplayListInt((ViewGroup) view, true); 3995 } 3996 } 3997 if (!invalidateThis) { 3998 return; 3999 } 4000 // we need to force it to become invisible 4001 if (viewGroup.getVisibility() == View.INVISIBLE) { 4002 viewGroup.setVisibility(View.VISIBLE); 4003 viewGroup.setVisibility(View.INVISIBLE); 4004 } else { 4005 final int visibility = viewGroup.getVisibility(); 4006 viewGroup.setVisibility(View.INVISIBLE); 4007 viewGroup.setVisibility(visibility); 4008 } 4009 } 4010 4011 /** 4012 * Recycle a detached view. The specified view will be added to a pool of views 4013 * for later rebinding and reuse. 4014 * 4015 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the 4016 * View is scrapped, it will be removed from scrap list.</p> 4017 * 4018 * @param view Removed view for recycling 4019 * @see LayoutManager#removeAndRecycleView(View, Recycler) 4020 */ 4021 public void recycleView(View view) { 4022 // This public recycle method tries to make view recycle-able since layout manager 4023 // intended to recycle this view (e.g. even if it is in scrap or change cache) 4024 ViewHolder holder = getChildViewHolderInt(view); 4025 if (holder.isTmpDetached()) { 4026 removeDetachedView(view, false); 4027 } 4028 if (holder.isScrap()) { 4029 holder.unScrap(); 4030 } else if (holder.wasReturnedFromScrap()){ 4031 holder.clearReturnedFromScrapFlag(); 4032 } 4033 recycleViewHolderInternal(holder); 4034 } 4035 4036 /** 4037 * Internally, use this method instead of {@link #recycleView(android.view.View)} to 4038 * catch potential bugs. 4039 * @param view 4040 */ 4041 void recycleViewInternal(View view) { 4042 recycleViewHolderInternal(getChildViewHolderInt(view)); 4043 } 4044 4045 void recycleAndClearCachedViews() { 4046 final int count = mCachedViews.size(); 4047 for (int i = count - 1; i >= 0; i--) { 4048 recycleCachedViewAt(i); 4049 } 4050 mCachedViews.clear(); 4051 } 4052 4053 /** 4054 * Recycles a cached view and removes the view from the list. Views are added to cache 4055 * if and only if they are recyclable, so this method does not check it again. 4056 * <p> 4057 * A small exception to this rule is when the view does not have an animator reference 4058 * but transient state is true (due to animations created outside ItemAnimator). In that 4059 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is 4060 * still recyclable since Adapter wants to do so. 4061 * 4062 * @param cachedViewIndex The index of the view in cached views list 4063 */ 4064 void recycleCachedViewAt(int cachedViewIndex) { 4065 if (DEBUG) { 4066 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); 4067 } 4068 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 4069 if (DEBUG) { 4070 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); 4071 } 4072 addViewHolderToRecycledViewPool(viewHolder); 4073 mCachedViews.remove(cachedViewIndex); 4074 } 4075 4076 /** 4077 * internal implementation checks if view is scrapped or attached and throws an exception 4078 * if so. 4079 * Public version un-scraps before calling recycle. 4080 */ 4081 void recycleViewHolderInternal(ViewHolder holder) { 4082 if (holder.isScrap() || holder.itemView.getParent() != null) { 4083 throw new IllegalArgumentException( 4084 "Scrapped or attached views may not be recycled. isScrap:" 4085 + holder.isScrap() + " isAttached:" 4086 + (holder.itemView.getParent() != null)); 4087 } 4088 4089 if (holder.isTmpDetached()) { 4090 throw new IllegalArgumentException("Tmp detached view should be removed " 4091 + "from RecyclerView before it can be recycled: " + holder); 4092 } 4093 4094 if (holder.shouldIgnore()) { 4095 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" 4096 + " should first call stopIgnoringView(view) before calling recycle."); 4097 } 4098 //noinspection unchecked 4099 final boolean transientStatePreventsRecycling = holder 4100 .doesTransientStatePreventRecycling(); 4101 final boolean forceRecycle = mAdapter != null 4102 && transientStatePreventsRecycling 4103 && mAdapter.onFailedToRecycleView(holder); 4104 boolean cached = false; 4105 boolean recycled = false; 4106 if (forceRecycle || holder.isRecyclable()) { 4107 if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) && 4108 !holder.isChanged()) { 4109 // Retire oldest cached view 4110 final int cachedViewSize = mCachedViews.size(); 4111 if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { 4112 recycleCachedViewAt(0); 4113 } 4114 if (cachedViewSize < mViewCacheMax) { 4115 mCachedViews.add(holder); 4116 cached = true; 4117 } 4118 } 4119 if (!cached) { 4120 addViewHolderToRecycledViewPool(holder); 4121 recycled = true; 4122 } 4123 } else if (DEBUG) { 4124 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " 4125 + "re-visit here. We are still removing it from animation lists"); 4126 } 4127 // even if the holder is not removed, we still call this method so that it is removed 4128 // from view holder lists. 4129 mState.onViewRecycled(holder); 4130 if (!cached && !recycled && transientStatePreventsRecycling) { 4131 holder.mOwnerRecyclerView = null; 4132 } 4133 } 4134 4135 void addViewHolderToRecycledViewPool(ViewHolder holder) { 4136 ViewCompat.setAccessibilityDelegate(holder.itemView, null); 4137 dispatchViewRecycled(holder); 4138 holder.mOwnerRecyclerView = null; 4139 getRecycledViewPool().putRecycledView(holder); 4140 } 4141 4142 /** 4143 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 4144 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 4145 * internal bookkeeping. 4146 */ 4147 void quickRecycleScrapView(View view) { 4148 final ViewHolder holder = getChildViewHolderInt(view); 4149 holder.mScrapContainer = null; 4150 holder.clearReturnedFromScrapFlag(); 4151 recycleViewHolderInternal(holder); 4152 } 4153 4154 /** 4155 * Mark an attached view as scrap. 4156 * 4157 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 4158 * for rebinding and reuse. Requests for a view for a given position may return a 4159 * reused or rebound scrap view instance.</p> 4160 * 4161 * @param view View to scrap 4162 */ 4163 void scrapView(View view) { 4164 final ViewHolder holder = getChildViewHolderInt(view); 4165 holder.setScrapContainer(this); 4166 if (!holder.isChanged() || !supportsChangeAnimations()) { 4167 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { 4168 throw new IllegalArgumentException("Called scrap view with an invalid view." 4169 + " Invalid views cannot be reused from scrap, they should rebound from" 4170 + " recycler pool."); 4171 } 4172 mAttachedScrap.add(holder); 4173 } else { 4174 if (mChangedScrap == null) { 4175 mChangedScrap = new ArrayList<ViewHolder>(); 4176 } 4177 mChangedScrap.add(holder); 4178 } 4179 } 4180 4181 /** 4182 * Remove a previously scrapped view from the pool of eligible scrap. 4183 * 4184 * <p>This view will no longer be eligible for reuse until re-scrapped or 4185 * until it is explicitly removed and recycled.</p> 4186 */ 4187 void unscrapView(ViewHolder holder) { 4188 if (!holder.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) { 4189 mAttachedScrap.remove(holder); 4190 } else { 4191 mChangedScrap.remove(holder); 4192 } 4193 holder.mScrapContainer = null; 4194 holder.clearReturnedFromScrapFlag(); 4195 } 4196 4197 int getScrapCount() { 4198 return mAttachedScrap.size(); 4199 } 4200 4201 View getScrapViewAt(int index) { 4202 return mAttachedScrap.get(index).itemView; 4203 } 4204 4205 void clearScrap() { 4206 mAttachedScrap.clear(); 4207 } 4208 4209 ViewHolder getChangedScrapViewForPosition(int position) { 4210 // If pre-layout, check the changed scrap for an exact match. 4211 final int changedScrapSize; 4212 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { 4213 return null; 4214 } 4215 // find by position 4216 for (int i = 0; i < changedScrapSize; i++) { 4217 final ViewHolder holder = mChangedScrap.get(i); 4218 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { 4219 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4220 return holder; 4221 } 4222 } 4223 // find by id 4224 if (mAdapter.hasStableIds()) { 4225 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 4226 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { 4227 final long id = mAdapter.getItemId(offsetPosition); 4228 for (int i = 0; i < changedScrapSize; i++) { 4229 final ViewHolder holder = mChangedScrap.get(i); 4230 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { 4231 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4232 return holder; 4233 } 4234 } 4235 } 4236 } 4237 return null; 4238 } 4239 4240 /** 4241 * Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if 4242 * ViewHolder's type matches the provided type. 4243 * 4244 * @param position Item position 4245 * @param type View type 4246 * @param dryRun Does a dry run, finds the ViewHolder but does not remove 4247 * @return a ViewHolder that can be re-used for this position. 4248 */ 4249 ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { 4250 final int scrapCount = mAttachedScrap.size(); 4251 4252 // Try first for an exact, non-invalid match from scrap. 4253 for (int i = 0; i < scrapCount; i++) { 4254 final ViewHolder holder = mAttachedScrap.get(i); 4255 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position 4256 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { 4257 if (type != INVALID_TYPE && holder.getItemViewType() != type) { 4258 Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + 4259 " wrong view type! (found " + holder.getItemViewType() + 4260 " but expected " + type + ")"); 4261 break; 4262 } 4263 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4264 return holder; 4265 } 4266 } 4267 4268 if (!dryRun) { 4269 View view = mChildHelper.findHiddenNonRemovedView(position, type); 4270 if (view != null) { 4271 // ending the animation should cause it to get recycled before we reuse it 4272 mItemAnimator.endAnimation(getChildViewHolder(view)); 4273 } 4274 } 4275 4276 // Search in our first-level recycled view cache. 4277 final int cacheSize = mCachedViews.size(); 4278 for (int i = 0; i < cacheSize; i++) { 4279 final ViewHolder holder = mCachedViews.get(i); 4280 // invalid view holders may be in cache if adapter has stable ids as they can be 4281 // retrieved via getScrapViewForId 4282 if (!holder.isInvalid() && holder.getLayoutPosition() == position) { 4283 if (!dryRun) { 4284 mCachedViews.remove(i); 4285 } 4286 if (DEBUG) { 4287 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 4288 ") found match in cache: " + holder); 4289 } 4290 return holder; 4291 } 4292 } 4293 return null; 4294 } 4295 4296 ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { 4297 // Look in our attached views first 4298 final int count = mAttachedScrap.size(); 4299 for (int i = count - 1; i >= 0; i--) { 4300 final ViewHolder holder = mAttachedScrap.get(i); 4301 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { 4302 if (type == holder.getItemViewType()) { 4303 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 4304 if (holder.isRemoved()) { 4305 // this might be valid in two cases: 4306 // > item is removed but we are in pre-layout pass 4307 // >> do nothing. return as is. make sure we don't rebind 4308 // > item is removed then added to another position and we are in 4309 // post layout. 4310 // >> remove removed and invalid flags, add update flag to rebind 4311 // because item was invisible to us and we don't know what happened in 4312 // between. 4313 if (!mState.isPreLayout()) { 4314 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE | 4315 ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); 4316 } 4317 } 4318 return holder; 4319 } else if (!dryRun) { 4320 // Recycle this scrap. Type mismatch. 4321 mAttachedScrap.remove(i); 4322 removeDetachedView(holder.itemView, false); 4323 quickRecycleScrapView(holder.itemView); 4324 } 4325 } 4326 } 4327 4328 // Search the first-level cache 4329 final int cacheSize = mCachedViews.size(); 4330 for (int i = cacheSize - 1; i >= 0; i--) { 4331 final ViewHolder holder = mCachedViews.get(i); 4332 if (holder.getItemId() == id) { 4333 if (type == holder.getItemViewType()) { 4334 if (!dryRun) { 4335 mCachedViews.remove(i); 4336 } 4337 return holder; 4338 } else if (!dryRun) { 4339 recycleCachedViewAt(i); 4340 } 4341 } 4342 } 4343 return null; 4344 } 4345 4346 void dispatchViewRecycled(ViewHolder holder) { 4347 if (mRecyclerListener != null) { 4348 mRecyclerListener.onViewRecycled(holder); 4349 } 4350 if (mAdapter != null) { 4351 mAdapter.onViewRecycled(holder); 4352 } 4353 if (mState != null) { 4354 mState.onViewRecycled(holder); 4355 } 4356 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); 4357 } 4358 4359 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 4360 boolean compatibleWithPrevious) { 4361 clear(); 4362 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); 4363 } 4364 4365 void offsetPositionRecordsForMove(int from, int to) { 4366 final int start, end, inBetweenOffset; 4367 if (from < to) { 4368 start = from; 4369 end = to; 4370 inBetweenOffset = -1; 4371 } else { 4372 start = to; 4373 end = from; 4374 inBetweenOffset = 1; 4375 } 4376 final int cachedCount = mCachedViews.size(); 4377 for (int i = 0; i < cachedCount; i++) { 4378 final ViewHolder holder = mCachedViews.get(i); 4379 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 4380 continue; 4381 } 4382 if (holder.mPosition == from) { 4383 holder.offsetPosition(to - from, false); 4384 } else { 4385 holder.offsetPosition(inBetweenOffset, false); 4386 } 4387 if (DEBUG) { 4388 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " + 4389 holder); 4390 } 4391 } 4392 } 4393 4394 void offsetPositionRecordsForInsert(int insertedAt, int count) { 4395 final int cachedCount = mCachedViews.size(); 4396 for (int i = 0; i < cachedCount; i++) { 4397 final ViewHolder holder = mCachedViews.get(i); 4398 if (holder != null && holder.getLayoutPosition() >= insertedAt) { 4399 if (DEBUG) { 4400 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + 4401 holder + " now at position " + (holder.mPosition + count)); 4402 } 4403 holder.offsetPosition(count, true); 4404 } 4405 } 4406 } 4407 4408 /** 4409 * @param removedFrom Remove start index 4410 * @param count Remove count 4411 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if 4412 * false, they'll be applied before the second layout pass 4413 */ 4414 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { 4415 final int removedEnd = removedFrom + count; 4416 final int cachedCount = mCachedViews.size(); 4417 for (int i = cachedCount - 1; i >= 0; i--) { 4418 final ViewHolder holder = mCachedViews.get(i); 4419 if (holder != null) { 4420 if (holder.getLayoutPosition() >= removedEnd) { 4421 if (DEBUG) { 4422 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + 4423 " holder " + holder + " now at position " + 4424 (holder.mPosition - count)); 4425 } 4426 holder.offsetPosition(-count, applyToPreLayout); 4427 } else if (holder.getLayoutPosition() >= removedFrom) { 4428 // Item for this view was removed. Dump it from the cache. 4429 holder.addFlags(ViewHolder.FLAG_REMOVED); 4430 recycleCachedViewAt(i); 4431 } 4432 } 4433 } 4434 } 4435 4436 void setViewCacheExtension(ViewCacheExtension extension) { 4437 mViewCacheExtension = extension; 4438 } 4439 4440 void setRecycledViewPool(RecycledViewPool pool) { 4441 if (mRecyclerPool != null) { 4442 mRecyclerPool.detach(); 4443 } 4444 mRecyclerPool = pool; 4445 if (pool != null) { 4446 mRecyclerPool.attach(getAdapter()); 4447 } 4448 } 4449 4450 RecycledViewPool getRecycledViewPool() { 4451 if (mRecyclerPool == null) { 4452 mRecyclerPool = new RecycledViewPool(); 4453 } 4454 return mRecyclerPool; 4455 } 4456 4457 void viewRangeUpdate(int positionStart, int itemCount) { 4458 final int positionEnd = positionStart + itemCount; 4459 final int cachedCount = mCachedViews.size(); 4460 for (int i = 0; i < cachedCount; i++) { 4461 final ViewHolder holder = mCachedViews.get(i); 4462 if (holder == null) { 4463 continue; 4464 } 4465 4466 final int pos = holder.getLayoutPosition(); 4467 if (pos >= positionStart && pos < positionEnd) { 4468 holder.addFlags(ViewHolder.FLAG_UPDATE); 4469 // cached views should not be flagged as changed because this will cause them 4470 // to animate when they are returned from cache. 4471 } 4472 } 4473 } 4474 4475 void setAdapterPositionsAsUnknown() { 4476 final int cachedCount = mCachedViews.size(); 4477 for (int i = 0; i < cachedCount; i++) { 4478 final ViewHolder holder = mCachedViews.get(i); 4479 if (holder != null) { 4480 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 4481 } 4482 } 4483 } 4484 4485 void markKnownViewsInvalid() { 4486 if (mAdapter != null && mAdapter.hasStableIds()) { 4487 final int cachedCount = mCachedViews.size(); 4488 for (int i = 0; i < cachedCount; i++) { 4489 final ViewHolder holder = mCachedViews.get(i); 4490 if (holder != null) { 4491 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 4492 } 4493 } 4494 } else { 4495 // we cannot re-use cached views in this case. Recycle them all 4496 recycleAndClearCachedViews(); 4497 } 4498 } 4499 4500 void clearOldPositions() { 4501 final int cachedCount = mCachedViews.size(); 4502 for (int i = 0; i < cachedCount; i++) { 4503 final ViewHolder holder = mCachedViews.get(i); 4504 holder.clearOldPosition(); 4505 } 4506 final int scrapCount = mAttachedScrap.size(); 4507 for (int i = 0; i < scrapCount; i++) { 4508 mAttachedScrap.get(i).clearOldPosition(); 4509 } 4510 if (mChangedScrap != null) { 4511 final int changedScrapCount = mChangedScrap.size(); 4512 for (int i = 0; i < changedScrapCount; i++) { 4513 mChangedScrap.get(i).clearOldPosition(); 4514 } 4515 } 4516 } 4517 4518 void markItemDecorInsetsDirty() { 4519 final int cachedCount = mCachedViews.size(); 4520 for (int i = 0; i < cachedCount; i++) { 4521 final ViewHolder holder = mCachedViews.get(i); 4522 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); 4523 if (layoutParams != null) { 4524 layoutParams.mInsetsDirty = true; 4525 } 4526 } 4527 } 4528 } 4529 4530 /** 4531 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can 4532 * ben controlled by the developer. 4533 * <p> 4534 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and 4535 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will 4536 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking 4537 * {@link RecycledViewPool}. 4538 * <p> 4539 * Note that, Recycler never sends Views to this method to be cached. It is developers 4540 * responsibility to decide whether they want to keep their Views in this custom cache or let 4541 * the default recycling policy handle it. 4542 */ 4543 public abstract static class ViewCacheExtension { 4544 4545 /** 4546 * Returns a View that can be binded to the given Adapter position. 4547 * <p> 4548 * This method should <b>not</b> create a new View. Instead, it is expected to return 4549 * an already created View that can be re-used for the given type and position. 4550 * If the View is marked as ignored, it should first call 4551 * {@link LayoutManager#stopIgnoringView(View)} before returning the View. 4552 * <p> 4553 * RecyclerView will re-bind the returned View to the position if necessary. 4554 * 4555 * @param recycler The Recycler that can be used to bind the View 4556 * @param position The adapter position 4557 * @param type The type of the View, defined by adapter 4558 * @return A View that is bound to the given position or NULL if there is no View to re-use 4559 * @see LayoutManager#ignoreView(View) 4560 */ 4561 abstract public View getViewForPositionAndType(Recycler recycler, int position, int type); 4562 } 4563 4564 /** 4565 * Base class for an Adapter 4566 * 4567 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 4568 * within a {@link RecyclerView}.</p> 4569 */ 4570 public static abstract class Adapter<VH extends ViewHolder> { 4571 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 4572 private boolean mHasStableIds = false; 4573 4574 /** 4575 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent 4576 * an item. 4577 * <p> 4578 * This new ViewHolder should be constructed with a new View that can represent the items 4579 * of the given type. You can either create a new View manually or inflate it from an XML 4580 * layout file. 4581 * <p> 4582 * The new ViewHolder will be used to display items of the adapter using 4583 * {@link #onBindViewHolder(ViewHolder, int)}. Since it will be re-used to display different 4584 * items in the data set, it is a good idea to cache references to sub views of the View to 4585 * avoid unnecessary {@link View#findViewById(int)} calls. 4586 * 4587 * @param parent The ViewGroup into which the new View will be added after it is bound to 4588 * an adapter position. 4589 * @param viewType The view type of the new View. 4590 * 4591 * @return A new ViewHolder that holds a View of the given view type. 4592 * @see #getItemViewType(int) 4593 * @see #onBindViewHolder(ViewHolder, int) 4594 */ 4595 public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); 4596 4597 /** 4598 * Called by RecyclerView to display the data at the specified position. This method 4599 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at 4600 * the given position. 4601 * <p> 4602 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this 4603 * method again if the position of the item changes in the data set unless the item itself 4604 * is invalidated or the new position cannot be determined. For this reason, you should only 4605 * use the <code>position</code> parameter while acquiring the related data item inside this 4606 * method and should not keep a copy of it. If you need the position of an item later on 4607 * (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will have 4608 * the updated adapter position. 4609 * 4610 * @param holder The ViewHolder which should be updated to represent the contents of the 4611 * item at the given position in the data set. 4612 * @param position The position of the item within the adapter's data set. 4613 */ 4614 public abstract void onBindViewHolder(VH holder, int position); 4615 4616 /** 4617 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new 4618 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. 4619 * 4620 * @see #onCreateViewHolder(ViewGroup, int) 4621 */ 4622 public final VH createViewHolder(ViewGroup parent, int viewType) { 4623 final VH holder = onCreateViewHolder(parent, viewType); 4624 holder.mItemViewType = viewType; 4625 return holder; 4626 } 4627 4628 /** 4629 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the 4630 * {@link ViewHolder} contents with the item at the given position and also sets up some 4631 * private fields to be used by RecyclerView. 4632 * 4633 * @see #onBindViewHolder(ViewHolder, int) 4634 */ 4635 public final void bindViewHolder(VH holder, int position) { 4636 holder.mPosition = position; 4637 if (hasStableIds()) { 4638 holder.mItemId = getItemId(position); 4639 } 4640 holder.setFlags(ViewHolder.FLAG_BOUND, 4641 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID 4642 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 4643 onBindViewHolder(holder, position); 4644 } 4645 4646 /** 4647 * Return the view type of the item at <code>position</code> for the purposes 4648 * of view recycling. 4649 * 4650 * <p>The default implementation of this method returns 0, making the assumption of 4651 * a single view type for the adapter. Unlike ListView adapters, types need not 4652 * be contiguous. Consider using id resources to uniquely identify item view types. 4653 * 4654 * @param position position to query 4655 * @return integer value identifying the type of the view needed to represent the item at 4656 * <code>position</code>. Type codes need not be contiguous. 4657 */ 4658 public int getItemViewType(int position) { 4659 return 0; 4660 } 4661 4662 /** 4663 * Indicates whether each item in the data set can be represented with a unique identifier 4664 * of type {@link java.lang.Long}. 4665 * 4666 * @param hasStableIds Whether items in data set have unique identifiers or not. 4667 * @see #hasStableIds() 4668 * @see #getItemId(int) 4669 */ 4670 public void setHasStableIds(boolean hasStableIds) { 4671 if (hasObservers()) { 4672 throw new IllegalStateException("Cannot change whether this adapter has " + 4673 "stable IDs while the adapter has registered observers."); 4674 } 4675 mHasStableIds = hasStableIds; 4676 } 4677 4678 /** 4679 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 4680 * would return false this method should return {@link #NO_ID}. The default implementation 4681 * of this method returns {@link #NO_ID}. 4682 * 4683 * @param position Adapter position to query 4684 * @return the stable ID of the item at position 4685 */ 4686 public long getItemId(int position) { 4687 return NO_ID; 4688 } 4689 4690 /** 4691 * Returns the total number of items in the data set hold by the adapter. 4692 * 4693 * @return The total number of items in this adapter. 4694 */ 4695 public abstract int getItemCount(); 4696 4697 /** 4698 * Returns true if this adapter publishes a unique <code>long</code> value that can 4699 * act as a key for the item at a given position in the data set. If that item is relocated 4700 * in the data set, the ID returned for that item should be the same. 4701 * 4702 * @return true if this adapter's items have stable IDs 4703 */ 4704 public final boolean hasStableIds() { 4705 return mHasStableIds; 4706 } 4707 4708 /** 4709 * Called when a view created by this adapter has been recycled. 4710 * 4711 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 4712 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 4713 * fallen out of visibility or a set of cached views represented by views still 4714 * attached to the parent RecyclerView. If an item view has large or expensive data 4715 * bound to it such as large bitmaps, this may be a good place to release those 4716 * resources.</p> 4717 * <p> 4718 * RecyclerView calls this method right before clearing ViewHolder's internal data and 4719 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 4720 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 4721 * its adapter position. 4722 * 4723 * @param holder The ViewHolder for the view being recycled 4724 */ 4725 public void onViewRecycled(VH holder) { 4726 } 4727 4728 /** 4729 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled 4730 * due to its transient state. Upon receiving this callback, Adapter can clear the 4731 * animation(s) that effect the View's transient state and return <code>true</code> so that 4732 * the View can be recycled. Keep in mind that the View in question is already removed from 4733 * the RecyclerView. 4734 * <p> 4735 * In some cases, it is acceptable to recycle a View although it has transient state. Most 4736 * of the time, this is a case where the transient state will be cleared in 4737 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. 4738 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return 4739 * value of this method to decide whether the View should be recycled or not. 4740 * <p> 4741 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you 4742 * should never receive this callback because RecyclerView keeps those Views as children 4743 * until their animations are complete. This callback is useful when children of the item 4744 * views create animations which may not be easy to implement using an {@link ItemAnimator}. 4745 * <p> 4746 * You should <em>never</em> fix this issue by calling 4747 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called 4748 * <code>holder.itemView.setHasTransientState(true);</code>. Each 4749 * <code>View.setHasTransientState(true)</code> call must be matched by a 4750 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View 4751 * may become inconsistent. You should always prefer to end or cancel animations that are 4752 * triggering the transient state instead of handling it manually. 4753 * 4754 * @param holder The ViewHolder containing the View that could not be recycled due to its 4755 * transient state. 4756 * @return True if the View should be recycled, false otherwise. Note that if this method 4757 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of 4758 * the View and recycle it regardless. If this method returns <code>false</code>, 4759 * RecyclerView will check the View's transient state again before giving a final decision. 4760 * Default implementation returns false. 4761 */ 4762 public boolean onFailedToRecycleView(VH holder) { 4763 return false; 4764 } 4765 4766 /** 4767 * Called when a view created by this adapter has been attached to a window. 4768 * 4769 * <p>This can be used as a reasonable signal that the view is about to be seen 4770 * by the user. If the adapter previously freed any resources in 4771 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 4772 * those resources should be restored here.</p> 4773 * 4774 * @param holder Holder of the view being attached 4775 */ 4776 public void onViewAttachedToWindow(VH holder) { 4777 } 4778 4779 /** 4780 * Called when a view created by this adapter has been detached from its window. 4781 * 4782 * <p>Becoming detached from the window is not necessarily a permanent condition; 4783 * the consumer of an Adapter's views may choose to cache views offscreen while they 4784 * are not visible, attaching an detaching them as appropriate.</p> 4785 * 4786 * @param holder Holder of the view being detached 4787 */ 4788 public void onViewDetachedFromWindow(VH holder) { 4789 } 4790 4791 /** 4792 * Returns true if one or more observers are attached to this adapter. 4793 * 4794 * @return true if this adapter has observers 4795 */ 4796 public final boolean hasObservers() { 4797 return mObservable.hasObservers(); 4798 } 4799 4800 /** 4801 * Register a new observer to listen for data changes. 4802 * 4803 * <p>The adapter may publish a variety of events describing specific changes. 4804 * Not all adapters may support all change types and some may fall back to a generic 4805 * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() 4806 * "something changed"} event if more specific data is not available.</p> 4807 * 4808 * <p>Components registering observers with an adapter are responsible for 4809 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 4810 * unregistering} those observers when finished.</p> 4811 * 4812 * @param observer Observer to register 4813 * 4814 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 4815 */ 4816 public void registerAdapterDataObserver(AdapterDataObserver observer) { 4817 mObservable.registerObserver(observer); 4818 } 4819 4820 /** 4821 * Unregister an observer currently listening for data changes. 4822 * 4823 * <p>The unregistered observer will no longer receive events about changes 4824 * to the adapter.</p> 4825 * 4826 * @param observer Observer to unregister 4827 * 4828 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) 4829 */ 4830 public void unregisterAdapterDataObserver(AdapterDataObserver observer) { 4831 mObservable.unregisterObserver(observer); 4832 } 4833 4834 /** 4835 * Called by RecyclerView when it starts observing this Adapter. 4836 * <p> 4837 * Keep in mind that same adapter may be observed by multiple RecyclerViews. 4838 * 4839 * @param recyclerView The RecyclerView instance which started observing this adapter. 4840 * @see #onDetachedFromRecyclerView(RecyclerView) 4841 */ 4842 public void onAttachedToRecyclerView(RecyclerView recyclerView) { 4843 } 4844 4845 /** 4846 * Called by RecyclerView when it stops observing this Adapter. 4847 * 4848 * @param recyclerView The RecyclerView instance which stopped observing this adapter. 4849 * @see #onAttachedToRecyclerView(RecyclerView) 4850 */ 4851 public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 4852 } 4853 4854 /** 4855 * Notify any registered observers that the data set has changed. 4856 * 4857 * <p>There are two different classes of data change events, item changes and structural 4858 * changes. Item changes are when a single item has its data updated but no positional 4859 * changes have occurred. Structural changes are when items are inserted, removed or moved 4860 * within the data set.</p> 4861 * 4862 * <p>This event does not specify what about the data set has changed, forcing 4863 * any observers to assume that all existing items and structure may no longer be valid. 4864 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 4865 * 4866 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 4867 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 4868 * this method is used. This can help for the purposes of animation and visual 4869 * object persistence but individual item views will still need to be rebound 4870 * and relaid out.</p> 4871 * 4872 * <p>If you are writing an adapter it will always be more efficient to use the more 4873 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 4874 * as a last resort.</p> 4875 * 4876 * @see #notifyItemChanged(int) 4877 * @see #notifyItemInserted(int) 4878 * @see #notifyItemRemoved(int) 4879 * @see #notifyItemRangeChanged(int, int) 4880 * @see #notifyItemRangeInserted(int, int) 4881 * @see #notifyItemRangeRemoved(int, int) 4882 */ 4883 public final void notifyDataSetChanged() { 4884 mObservable.notifyChanged(); 4885 } 4886 4887 /** 4888 * Notify any registered observers that the item at <code>position</code> has changed. 4889 * 4890 * <p>This is an item change event, not a structural change event. It indicates that any 4891 * reflection of the data at <code>position</code> is out of date and should be updated. 4892 * The item at <code>position</code> retains the same identity.</p> 4893 * 4894 * @param position Position of the item that has changed 4895 * 4896 * @see #notifyItemRangeChanged(int, int) 4897 */ 4898 public final void notifyItemChanged(int position) { 4899 mObservable.notifyItemRangeChanged(position, 1); 4900 } 4901 4902 /** 4903 * Notify any registered observers that the <code>itemCount</code> items starting at 4904 * position <code>positionStart</code> have changed. 4905 * 4906 * <p>This is an item change event, not a structural change event. It indicates that 4907 * any reflection of the data in the given position range is out of date and should 4908 * be updated. The items in the given range retain the same identity.</p> 4909 * 4910 * @param positionStart Position of the first item that has changed 4911 * @param itemCount Number of items that have changed 4912 * 4913 * @see #notifyItemChanged(int) 4914 */ 4915 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 4916 mObservable.notifyItemRangeChanged(positionStart, itemCount); 4917 } 4918 4919 /** 4920 * Notify any registered observers that the item reflected at <code>position</code> 4921 * has been newly inserted. The item previously at <code>position</code> is now at 4922 * position <code>position + 1</code>. 4923 * 4924 * <p>This is a structural change event. Representations of other existing items in the 4925 * data set are still considered up to date and will not be rebound, though their 4926 * positions may be altered.</p> 4927 * 4928 * @param position Position of the newly inserted item in the data set 4929 * 4930 * @see #notifyItemRangeInserted(int, int) 4931 */ 4932 public final void notifyItemInserted(int position) { 4933 mObservable.notifyItemRangeInserted(position, 1); 4934 } 4935 4936 /** 4937 * Notify any registered observers that the item reflected at <code>fromPosition</code> 4938 * has been moved to <code>toPosition</code>. 4939 * 4940 * <p>This is a structural change event. Representations of other existing items in the 4941 * data set are still considered up to date and will not be rebound, though their 4942 * positions may be altered.</p> 4943 * 4944 * @param fromPosition Previous position of the item. 4945 * @param toPosition New position of the item. 4946 */ 4947 public final void notifyItemMoved(int fromPosition, int toPosition) { 4948 mObservable.notifyItemMoved(fromPosition, toPosition); 4949 } 4950 4951 /** 4952 * Notify any registered observers that the currently reflected <code>itemCount</code> 4953 * items starting at <code>positionStart</code> have been newly inserted. The items 4954 * previously located at <code>positionStart</code> and beyond can now be found starting 4955 * at position <code>positionStart + itemCount</code>. 4956 * 4957 * <p>This is a structural change event. Representations of other existing items in the 4958 * data set are still considered up to date and will not be rebound, though their positions 4959 * may be altered.</p> 4960 * 4961 * @param positionStart Position of the first item that was inserted 4962 * @param itemCount Number of items inserted 4963 * 4964 * @see #notifyItemInserted(int) 4965 */ 4966 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 4967 mObservable.notifyItemRangeInserted(positionStart, itemCount); 4968 } 4969 4970 /** 4971 * Notify any registered observers that the item previously located at <code>position</code> 4972 * has been removed from the data set. The items previously located at and after 4973 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 4974 * 4975 * <p>This is a structural change event. Representations of other existing items in the 4976 * data set are still considered up to date and will not be rebound, though their positions 4977 * may be altered.</p> 4978 * 4979 * @param position Position of the item that has now been removed 4980 * 4981 * @see #notifyItemRangeRemoved(int, int) 4982 */ 4983 public final void notifyItemRemoved(int position) { 4984 mObservable.notifyItemRangeRemoved(position, 1); 4985 } 4986 4987 /** 4988 * Notify any registered observers that the <code>itemCount</code> items previously 4989 * located at <code>positionStart</code> have been removed from the data set. The items 4990 * previously located at and after <code>positionStart + itemCount</code> may now be found 4991 * at <code>oldPosition - itemCount</code>. 4992 * 4993 * <p>This is a structural change event. Representations of other existing items in the data 4994 * set are still considered up to date and will not be rebound, though their positions 4995 * may be altered.</p> 4996 * 4997 * @param positionStart Previous position of the first item that was removed 4998 * @param itemCount Number of items removed from the data set 4999 */ 5000 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 5001 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 5002 } 5003 } 5004 5005 private void dispatchChildDetached(View child) { 5006 if (mAdapter != null) { 5007 mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); 5008 } 5009 onChildDetachedFromWindow(child); 5010 } 5011 5012 private void dispatchChildAttached(View child) { 5013 if (mAdapter != null) { 5014 mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child)); 5015 } 5016 onChildAttachedToWindow(child); 5017 } 5018 5019 /** 5020 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 5021 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 5022 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 5023 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 5024 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 5025 * layout managers are provided for general use. 5026 */ 5027 public static abstract class LayoutManager { 5028 ChildHelper mChildHelper; 5029 RecyclerView mRecyclerView; 5030 5031 @Nullable 5032 SmoothScroller mSmoothScroller; 5033 5034 private boolean mRequestedSimpleAnimations = false; 5035 5036 void setRecyclerView(RecyclerView recyclerView) { 5037 if (recyclerView == null) { 5038 mRecyclerView = null; 5039 mChildHelper = null; 5040 } else { 5041 mRecyclerView = recyclerView; 5042 mChildHelper = recyclerView.mChildHelper; 5043 } 5044 5045 } 5046 5047 /** 5048 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView 5049 */ 5050 public void requestLayout() { 5051 if(mRecyclerView != null) { 5052 mRecyclerView.requestLayout(); 5053 } 5054 } 5055 5056 /** 5057 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 5058 * {@link IllegalStateException} if it <b>is not</b>. 5059 * 5060 * @param message The message for the exception. Can be null. 5061 * @see #assertNotInLayoutOrScroll(String) 5062 */ 5063 public void assertInLayoutOrScroll(String message) { 5064 if (mRecyclerView != null) { 5065 mRecyclerView.assertInLayoutOrScroll(message); 5066 } 5067 } 5068 5069 /** 5070 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 5071 * {@link IllegalStateException} if it <b>is</b>. 5072 * 5073 * @param message The message for the exception. Can be null. 5074 * @see #assertInLayoutOrScroll(String) 5075 */ 5076 public void assertNotInLayoutOrScroll(String message) { 5077 if (mRecyclerView != null) { 5078 mRecyclerView.assertNotInLayoutOrScroll(message); 5079 } 5080 } 5081 5082 /** 5083 * Returns whether this LayoutManager supports automatic item animations. 5084 * A LayoutManager wishing to support item animations should obey certain 5085 * rules as outlined in {@link #onLayoutChildren(Recycler, State)}. 5086 * The default return value is <code>false</code>, so subclasses of LayoutManager 5087 * will not get predictive item animations by default. 5088 * 5089 * <p>Whether item animations are enabled in a RecyclerView is determined both 5090 * by the return value from this method and the 5091 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the 5092 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this 5093 * method returns false, then simple item animations will be enabled, in which 5094 * views that are moving onto or off of the screen are simply faded in/out. If 5095 * the RecyclerView has a non-null ItemAnimator and this method returns true, 5096 * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to 5097 * setup up the information needed to more intelligently predict where appearing 5098 * and disappearing views should be animated from/to.</p> 5099 * 5100 * @return true if predictive item animations should be enabled, false otherwise 5101 */ 5102 public boolean supportsPredictiveItemAnimations() { 5103 return false; 5104 } 5105 5106 /** 5107 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 5108 * is attached to a window. 5109 * 5110 * <p>Subclass implementations should always call through to the superclass implementation. 5111 * </p> 5112 * 5113 * @param view The RecyclerView this LayoutManager is bound to 5114 */ 5115 @CallSuper 5116 public void onAttachedToWindow(RecyclerView view) { 5117 } 5118 5119 /** 5120 * @deprecated 5121 * override {@link #onDetachedFromWindow(RecyclerView, Recycler)} 5122 */ 5123 @Deprecated 5124 public void onDetachedFromWindow(RecyclerView view) { 5125 5126 } 5127 5128 /** 5129 * Called when this LayoutManager is detached from its parent RecyclerView or when 5130 * its parent RecyclerView is detached from its window. 5131 * 5132 * <p>Subclass implementations should always call through to the superclass implementation. 5133 * </p> 5134 * 5135 * @param view The RecyclerView this LayoutManager is bound to 5136 * @param recycler The recycler to use if you prefer to recycle your children instead of 5137 * keeping them around. 5138 */ 5139 @CallSuper 5140 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { 5141 onDetachedFromWindow(view); 5142 } 5143 5144 /** 5145 * Check if the RecyclerView is configured to clip child views to its padding. 5146 * 5147 * @return true if this RecyclerView clips children to its padding, false otherwise 5148 */ 5149 public boolean getClipToPadding() { 5150 return mRecyclerView != null && mRecyclerView.mClipToPadding; 5151 } 5152 5153 /** 5154 * Lay out all relevant child views from the given adapter. 5155 * 5156 * The LayoutManager is in charge of the behavior of item animations. By default, 5157 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple 5158 * item animations are enabled. This means that add/remove operations on the 5159 * adapter will result in animations to add new or appearing items, removed or 5160 * disappearing items, and moved items. If a LayoutManager returns false from 5161 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a 5162 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the 5163 * RecyclerView will have enough information to run those animations in a simple 5164 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will 5165 * simple fade views in and out, whether they are actuall added/removed or whether 5166 * they are moved on or off the screen due to other add/remove operations. 5167 * 5168 * <p>A LayoutManager wanting a better item animation experience, where items can be 5169 * animated onto and off of the screen according to where the items exist when they 5170 * are not on screen, then the LayoutManager should return true from 5171 * {@link #supportsPredictiveItemAnimations()} and add additional logic to 5172 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations 5173 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; 5174 * once as a "pre" layout step to determine where items would have been prior to 5175 * a real layout, and again to do the "real" layout. In the pre-layout phase, 5176 * items will remember their pre-layout positions to allow them to be laid out 5177 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will 5178 * be returned from the scrap to help determine correct placement of other items. 5179 * These removed items should not be added to the child list, but should be used 5180 * to help calculate correct positioning of other views, including views that 5181 * were not previously onscreen (referred to as APPEARING views), but whose 5182 * pre-layout offscreen position can be determined given the extra 5183 * information about the pre-layout removed views.</p> 5184 * 5185 * <p>The second layout pass is the real layout in which only non-removed views 5186 * will be used. The only additional requirement during this pass is, if 5187 * {@link #supportsPredictiveItemAnimations()} returns true, to note which 5188 * views exist in the child list prior to layout and which are not there after 5189 * layout (referred to as DISAPPEARING views), and to position/layout those views 5190 * appropriately, without regard to the actual bounds of the RecyclerView. This allows 5191 * the animation system to know the location to which to animate these disappearing 5192 * views.</p> 5193 * 5194 * <p>The default LayoutManager implementations for RecyclerView handle all of these 5195 * requirements for animations already. Clients of RecyclerView can either use one 5196 * of these layout managers directly or look at their implementations of 5197 * onLayoutChildren() to see how they account for the APPEARING and 5198 * DISAPPEARING views.</p> 5199 * 5200 * @param recycler Recycler to use for fetching potentially cached views for a 5201 * position 5202 * @param state Transient state of RecyclerView 5203 */ 5204 public void onLayoutChildren(Recycler recycler, State state) { 5205 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); 5206 } 5207 5208 /** 5209 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 5210 * 5211 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 5212 * to store extra information specific to the layout. Client code should subclass 5213 * {@link RecyclerView.LayoutParams} for this purpose.</p> 5214 * 5215 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 5216 * you must also override 5217 * {@link #checkLayoutParams(LayoutParams)}, 5218 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 5219 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 5220 * 5221 * @return A new LayoutParams for a child view 5222 */ 5223 public abstract LayoutParams generateDefaultLayoutParams(); 5224 5225 /** 5226 * Determines the validity of the supplied LayoutParams object. 5227 * 5228 * <p>This should check to make sure that the object is of the correct type 5229 * and all values are within acceptable ranges. The default implementation 5230 * returns <code>true</code> for non-null params.</p> 5231 * 5232 * @param lp LayoutParams object to check 5233 * @return true if this LayoutParams object is valid, false otherwise 5234 */ 5235 public boolean checkLayoutParams(LayoutParams lp) { 5236 return lp != null; 5237 } 5238 5239 /** 5240 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 5241 * values from the supplied LayoutParams object if possible. 5242 * 5243 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 5244 * you must also override 5245 * {@link #checkLayoutParams(LayoutParams)}, 5246 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 5247 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 5248 * 5249 * @param lp Source LayoutParams object to copy values from 5250 * @return a new LayoutParams object 5251 */ 5252 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 5253 if (lp instanceof LayoutParams) { 5254 return new LayoutParams((LayoutParams) lp); 5255 } else if (lp instanceof MarginLayoutParams) { 5256 return new LayoutParams((MarginLayoutParams) lp); 5257 } else { 5258 return new LayoutParams(lp); 5259 } 5260 } 5261 5262 /** 5263 * Create a LayoutParams object suitable for this LayoutManager from 5264 * an inflated layout resource. 5265 * 5266 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 5267 * you must also override 5268 * {@link #checkLayoutParams(LayoutParams)}, 5269 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 5270 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 5271 * 5272 * @param c Context for obtaining styled attributes 5273 * @param attrs AttributeSet describing the supplied arguments 5274 * @return a new LayoutParams object 5275 */ 5276 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 5277 return new LayoutParams(c, attrs); 5278 } 5279 5280 /** 5281 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 5282 * The default implementation does nothing and returns 0. 5283 * 5284 * @param dx distance to scroll by in pixels. X increases as scroll position 5285 * approaches the right. 5286 * @param recycler Recycler to use for fetching potentially cached views for a 5287 * position 5288 * @param state Transient state of RecyclerView 5289 * @return The actual distance scrolled. The return value will be negative if dx was 5290 * negative and scrolling proceeeded in that direction. 5291 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 5292 */ 5293 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { 5294 return 0; 5295 } 5296 5297 /** 5298 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 5299 * The default implementation does nothing and returns 0. 5300 * 5301 * @param dy distance to scroll in pixels. Y increases as scroll position 5302 * approaches the bottom. 5303 * @param recycler Recycler to use for fetching potentially cached views for a 5304 * position 5305 * @param state Transient state of RecyclerView 5306 * @return The actual distance scrolled. The return value will be negative if dy was 5307 * negative and scrolling proceeeded in that direction. 5308 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 5309 */ 5310 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { 5311 return 0; 5312 } 5313 5314 /** 5315 * Query if horizontal scrolling is currently supported. The default implementation 5316 * returns false. 5317 * 5318 * @return True if this LayoutManager can scroll the current contents horizontally 5319 */ 5320 public boolean canScrollHorizontally() { 5321 return false; 5322 } 5323 5324 /** 5325 * Query if vertical scrolling is currently supported. The default implementation 5326 * returns false. 5327 * 5328 * @return True if this LayoutManager can scroll the current contents vertically 5329 */ 5330 public boolean canScrollVertically() { 5331 return false; 5332 } 5333 5334 /** 5335 * Scroll to the specified adapter position. 5336 * 5337 * Actual position of the item on the screen depends on the LayoutManager implementation. 5338 * @param position Scroll to this adapter position. 5339 */ 5340 public void scrollToPosition(int position) { 5341 if (DEBUG) { 5342 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); 5343 } 5344 } 5345 5346 /** 5347 * <p>Smooth scroll to the specified adapter position.</p> 5348 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} 5349 * instance and call {@link #startSmoothScroll(SmoothScroller)}. 5350 * </p> 5351 * @param recyclerView The RecyclerView to which this layout manager is attached 5352 * @param state Current State of RecyclerView 5353 * @param position Scroll to this adapter position. 5354 */ 5355 public void smoothScrollToPosition(RecyclerView recyclerView, State state, 5356 int position) { 5357 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); 5358 } 5359 5360 /** 5361 * <p>Starts a smooth scroll using the provided SmoothScroller.</p> 5362 * <p>Calling this method will cancel any previous smooth scroll request.</p> 5363 * @param smoothScroller Unstance which defines how smooth scroll should be animated 5364 */ 5365 public void startSmoothScroll(SmoothScroller smoothScroller) { 5366 if (mSmoothScroller != null && smoothScroller != mSmoothScroller 5367 && mSmoothScroller.isRunning()) { 5368 mSmoothScroller.stop(); 5369 } 5370 mSmoothScroller = smoothScroller; 5371 mSmoothScroller.start(mRecyclerView, this); 5372 } 5373 5374 /** 5375 * @return true if RecycylerView is currently in the state of smooth scrolling. 5376 */ 5377 public boolean isSmoothScrolling() { 5378 return mSmoothScroller != null && mSmoothScroller.isRunning(); 5379 } 5380 5381 5382 /** 5383 * Returns the resolved layout direction for this RecyclerView. 5384 * 5385 * @return {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout 5386 * direction is RTL or returns 5387 * {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction 5388 * is not RTL. 5389 */ 5390 public int getLayoutDirection() { 5391 return ViewCompat.getLayoutDirection(mRecyclerView); 5392 } 5393 5394 /** 5395 * Ends all animations on the view created by the {@link ItemAnimator}. 5396 * 5397 * @param view The View for which the animations should be ended. 5398 * @see RecyclerView.ItemAnimator#endAnimations() 5399 */ 5400 public void endAnimation(View view) { 5401 if (mRecyclerView.mItemAnimator != null) { 5402 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); 5403 } 5404 } 5405 5406 /** 5407 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 5408 * to the layout that is known to be going away, either because it has been 5409 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 5410 * visible portion of the container but is being laid out in order to inform RecyclerView 5411 * in how to animate the item out of view. 5412 * <p> 5413 * Views added via this method are going to be invisible to LayoutManager after the 5414 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 5415 * or won't be included in {@link #getChildCount()} method. 5416 * 5417 * @param child View to add and then remove with animation. 5418 */ 5419 public void addDisappearingView(View child) { 5420 addDisappearingView(child, -1); 5421 } 5422 5423 /** 5424 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 5425 * to the layout that is known to be going away, either because it has been 5426 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 5427 * visible portion of the container but is being laid out in order to inform RecyclerView 5428 * in how to animate the item out of view. 5429 * <p> 5430 * Views added via this method are going to be invisible to LayoutManager after the 5431 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 5432 * or won't be included in {@link #getChildCount()} method. 5433 * 5434 * @param child View to add and then remove with animation. 5435 * @param index Index of the view. 5436 */ 5437 public void addDisappearingView(View child, int index) { 5438 addViewInt(child, index, true); 5439 } 5440 5441 /** 5442 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 5443 * use this method to add views obtained from a {@link Recycler} using 5444 * {@link Recycler#getViewForPosition(int)}. 5445 * 5446 * @param child View to add 5447 */ 5448 public void addView(View child) { 5449 addView(child, -1); 5450 } 5451 5452 /** 5453 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 5454 * use this method to add views obtained from a {@link Recycler} using 5455 * {@link Recycler#getViewForPosition(int)}. 5456 * 5457 * @param child View to add 5458 * @param index Index to add child at 5459 */ 5460 public void addView(View child, int index) { 5461 addViewInt(child, index, false); 5462 } 5463 5464 private void addViewInt(View child, int index, boolean disappearing) { 5465 final ViewHolder holder = getChildViewHolderInt(child); 5466 if (disappearing || holder.isRemoved()) { 5467 // these views will be hidden at the end of the layout pass. 5468 mRecyclerView.addToDisappearingList(child); 5469 } else { 5470 // This may look like unnecessary but may happen if layout manager supports 5471 // predictive layouts and adapter removed then re-added the same item. 5472 // In this case, added version will be visible in the post layout (because add is 5473 // deferred) but RV will still bind it to the same View. 5474 // So if a View re-appears in post layout pass, remove it from disappearing list. 5475 mRecyclerView.removeFromDisappearingList(child); 5476 } 5477 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 5478 if (holder.wasReturnedFromScrap() || holder.isScrap()) { 5479 if (holder.isScrap()) { 5480 holder.unScrap(); 5481 } else { 5482 holder.clearReturnedFromScrapFlag(); 5483 } 5484 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); 5485 if (DISPATCH_TEMP_DETACH) { 5486 ViewCompat.dispatchFinishTemporaryDetach(child); 5487 } 5488 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child 5489 // ensure in correct position 5490 int currentIndex = mChildHelper.indexOfChild(child); 5491 if (index == -1) { 5492 index = mChildHelper.getChildCount(); 5493 } 5494 if (currentIndex == -1) { 5495 throw new IllegalStateException("Added View has RecyclerView as parent but" 5496 + " view is not a real child. Unfiltered index:" 5497 + mRecyclerView.indexOfChild(child)); 5498 } 5499 if (currentIndex != index) { 5500 mRecyclerView.mLayout.moveView(currentIndex, index); 5501 } 5502 } else { 5503 mChildHelper.addView(child, index, false); 5504 lp.mInsetsDirty = true; 5505 if (mSmoothScroller != null && mSmoothScroller.isRunning()) { 5506 mSmoothScroller.onChildAttachedToWindow(child); 5507 } 5508 } 5509 if (lp.mPendingInvalidate) { 5510 if (DEBUG) { 5511 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); 5512 } 5513 holder.itemView.invalidate(); 5514 lp.mPendingInvalidate = false; 5515 } 5516 } 5517 5518 /** 5519 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 5520 * use this method to completely remove a child view that is no longer needed. 5521 * LayoutManagers should strongly consider recycling removed views using 5522 * {@link Recycler#recycleView(android.view.View)}. 5523 * 5524 * @param child View to remove 5525 */ 5526 public void removeView(View child) { 5527 mChildHelper.removeView(child); 5528 } 5529 5530 /** 5531 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 5532 * use this method to completely remove a child view that is no longer needed. 5533 * LayoutManagers should strongly consider recycling removed views using 5534 * {@link Recycler#recycleView(android.view.View)}. 5535 * 5536 * @param index Index of the child view to remove 5537 */ 5538 public void removeViewAt(int index) { 5539 final View child = getChildAt(index); 5540 if (child != null) { 5541 mChildHelper.removeViewAt(index); 5542 } 5543 } 5544 5545 /** 5546 * Remove all views from the currently attached RecyclerView. This will not recycle 5547 * any of the affected views; the LayoutManager is responsible for doing so if desired. 5548 */ 5549 public void removeAllViews() { 5550 // Only remove non-animating views 5551 final int childCount = getChildCount(); 5552 for (int i = childCount - 1; i >= 0; i--) { 5553 mChildHelper.removeViewAt(i); 5554 } 5555 } 5556 5557 /** 5558 * Returns offset of the RecyclerView's text baseline from the its top boundary. 5559 * 5560 * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if 5561 * there is no baseline. 5562 */ 5563 public int getBaseline() { 5564 return -1; 5565 } 5566 5567 /** 5568 * Returns the adapter position of the item represented by the given View. This does not 5569 * contain any adapter changes that might have happened after the last layout. 5570 * 5571 * @param view The view to query 5572 * @return The adapter position of the item which is rendered by this View. 5573 */ 5574 public int getPosition(View view) { 5575 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 5576 } 5577 5578 /** 5579 * Returns the View type defined by the adapter. 5580 * 5581 * @param view The view to query 5582 * @return The type of the view assigned by the adapter. 5583 */ 5584 public int getItemViewType(View view) { 5585 return getChildViewHolderInt(view).getItemViewType(); 5586 } 5587 5588 /** 5589 * Finds the view which represents the given adapter position. 5590 * <p> 5591 * This method traverses each child since it has no information about child order. 5592 * Override this method to improve performance if your LayoutManager keeps data about 5593 * child views. 5594 * <p> 5595 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. 5596 * 5597 * @param position Position of the item in adapter 5598 * @return The child view that represents the given position or null if the position is not 5599 * laid out 5600 */ 5601 public View findViewByPosition(int position) { 5602 final int childCount = getChildCount(); 5603 for (int i = 0; i < childCount; i++) { 5604 View child = getChildAt(i); 5605 ViewHolder vh = getChildViewHolderInt(child); 5606 if (vh == null) { 5607 continue; 5608 } 5609 if (vh.getLayoutPosition() == position && !vh.shouldIgnore() && 5610 (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { 5611 return child; 5612 } 5613 } 5614 return null; 5615 } 5616 5617 /** 5618 * Temporarily detach a child view. 5619 * 5620 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 5621 * views currently attached to the RecyclerView. Generally LayoutManager implementations 5622 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 5623 * so that the detached view may be rebound and reused.</p> 5624 * 5625 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 5626 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 5627 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 5628 * before the LayoutManager entry point method called by RecyclerView returns.</p> 5629 * 5630 * @param child Child to detach 5631 */ 5632 public void detachView(View child) { 5633 final int ind = mChildHelper.indexOfChild(child); 5634 if (ind >= 0) { 5635 detachViewInternal(ind, child); 5636 } 5637 } 5638 5639 /** 5640 * Temporarily detach a child view. 5641 * 5642 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 5643 * views currently attached to the RecyclerView. Generally LayoutManager implementations 5644 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 5645 * so that the detached view may be rebound and reused.</p> 5646 * 5647 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 5648 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 5649 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 5650 * before the LayoutManager entry point method called by RecyclerView returns.</p> 5651 * 5652 * @param index Index of the child to detach 5653 */ 5654 public void detachViewAt(int index) { 5655 detachViewInternal(index, getChildAt(index)); 5656 } 5657 5658 private void detachViewInternal(int index, View view) { 5659 if (DISPATCH_TEMP_DETACH) { 5660 ViewCompat.dispatchStartTemporaryDetach(view); 5661 } 5662 mChildHelper.detachViewFromParent(index); 5663 } 5664 5665 /** 5666 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5667 * This method should not be used to reattach views that were previously 5668 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5669 * 5670 * @param child Child to reattach 5671 * @param index Intended child index for child 5672 * @param lp LayoutParams for child 5673 */ 5674 public void attachView(View child, int index, LayoutParams lp) { 5675 ViewHolder vh = getChildViewHolderInt(child); 5676 if (vh.isRemoved()) { 5677 mRecyclerView.addToDisappearingList(child); 5678 } else { 5679 mRecyclerView.removeFromDisappearingList(child); 5680 } 5681 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); 5682 if (DISPATCH_TEMP_DETACH) { 5683 ViewCompat.dispatchFinishTemporaryDetach(child); 5684 } 5685 } 5686 5687 /** 5688 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5689 * This method should not be used to reattach views that were previously 5690 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5691 * 5692 * @param child Child to reattach 5693 * @param index Intended child index for child 5694 */ 5695 public void attachView(View child, int index) { 5696 attachView(child, index, (LayoutParams) child.getLayoutParams()); 5697 } 5698 5699 /** 5700 * Reattach a previously {@link #detachView(android.view.View) detached} view. 5701 * This method should not be used to reattach views that were previously 5702 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 5703 * 5704 * @param child Child to reattach 5705 */ 5706 public void attachView(View child) { 5707 attachView(child, -1); 5708 } 5709 5710 /** 5711 * Finish removing a view that was previously temporarily 5712 * {@link #detachView(android.view.View) detached}. 5713 * 5714 * @param child Detached child to remove 5715 */ 5716 public void removeDetachedView(View child) { 5717 mRecyclerView.removeDetachedView(child, false); 5718 } 5719 5720 /** 5721 * Moves a View from one position to another. 5722 * 5723 * @param fromIndex The View's initial index 5724 * @param toIndex The View's target index 5725 */ 5726 public void moveView(int fromIndex, int toIndex) { 5727 View view = getChildAt(fromIndex); 5728 if (view == null) { 5729 throw new IllegalArgumentException("Cannot move a child from non-existing index:" 5730 + fromIndex); 5731 } 5732 detachViewAt(fromIndex); 5733 attachView(view, toIndex); 5734 } 5735 5736 /** 5737 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 5738 * 5739 * <p>Scrapping a view allows it to be rebound and reused to show updated or 5740 * different data.</p> 5741 * 5742 * @param child Child to detach and scrap 5743 * @param recycler Recycler to deposit the new scrap view into 5744 */ 5745 public void detachAndScrapView(View child, Recycler recycler) { 5746 int index = mChildHelper.indexOfChild(child); 5747 scrapOrRecycleView(recycler, index, child); 5748 } 5749 5750 /** 5751 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 5752 * 5753 * <p>Scrapping a view allows it to be rebound and reused to show updated or 5754 * different data.</p> 5755 * 5756 * @param index Index of child to detach and scrap 5757 * @param recycler Recycler to deposit the new scrap view into 5758 */ 5759 public void detachAndScrapViewAt(int index, Recycler recycler) { 5760 final View child = getChildAt(index); 5761 scrapOrRecycleView(recycler, index, child); 5762 } 5763 5764 /** 5765 * Remove a child view and recycle it using the given Recycler. 5766 * 5767 * @param child Child to remove and recycle 5768 * @param recycler Recycler to use to recycle child 5769 */ 5770 public void removeAndRecycleView(View child, Recycler recycler) { 5771 removeView(child); 5772 recycler.recycleView(child); 5773 } 5774 5775 /** 5776 * Remove a child view and recycle it using the given Recycler. 5777 * 5778 * @param index Index of child to remove and recycle 5779 * @param recycler Recycler to use to recycle child 5780 */ 5781 public void removeAndRecycleViewAt(int index, Recycler recycler) { 5782 final View view = getChildAt(index); 5783 removeViewAt(index); 5784 recycler.recycleView(view); 5785 } 5786 5787 /** 5788 * Return the current number of child views attached to the parent RecyclerView. 5789 * This does not include child views that were temporarily detached and/or scrapped. 5790 * 5791 * @return Number of attached children 5792 */ 5793 public int getChildCount() { 5794 return mChildHelper != null ? mChildHelper.getChildCount() : 0; 5795 } 5796 5797 /** 5798 * Return the child view at the given index 5799 * @param index Index of child to return 5800 * @return Child view at index 5801 */ 5802 public View getChildAt(int index) { 5803 return mChildHelper != null ? mChildHelper.getChildAt(index) : null; 5804 } 5805 5806 /** 5807 * Return the width of the parent RecyclerView 5808 * 5809 * @return Width in pixels 5810 */ 5811 public int getWidth() { 5812 return mRecyclerView != null ? mRecyclerView.getWidth() : 0; 5813 } 5814 5815 /** 5816 * Return the height of the parent RecyclerView 5817 * 5818 * @return Height in pixels 5819 */ 5820 public int getHeight() { 5821 return mRecyclerView != null ? mRecyclerView.getHeight() : 0; 5822 } 5823 5824 /** 5825 * Return the left padding of the parent RecyclerView 5826 * 5827 * @return Padding in pixels 5828 */ 5829 public int getPaddingLeft() { 5830 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 5831 } 5832 5833 /** 5834 * Return the top padding of the parent RecyclerView 5835 * 5836 * @return Padding in pixels 5837 */ 5838 public int getPaddingTop() { 5839 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 5840 } 5841 5842 /** 5843 * Return the right padding of the parent RecyclerView 5844 * 5845 * @return Padding in pixels 5846 */ 5847 public int getPaddingRight() { 5848 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 5849 } 5850 5851 /** 5852 * Return the bottom padding of the parent RecyclerView 5853 * 5854 * @return Padding in pixels 5855 */ 5856 public int getPaddingBottom() { 5857 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 5858 } 5859 5860 /** 5861 * Return the start padding of the parent RecyclerView 5862 * 5863 * @return Padding in pixels 5864 */ 5865 public int getPaddingStart() { 5866 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 5867 } 5868 5869 /** 5870 * Return the end padding of the parent RecyclerView 5871 * 5872 * @return Padding in pixels 5873 */ 5874 public int getPaddingEnd() { 5875 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 5876 } 5877 5878 /** 5879 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 5880 * 5881 * @return True if the RecyclerView has focus, false otherwise. 5882 * @see View#isFocused() 5883 */ 5884 public boolean isFocused() { 5885 return mRecyclerView != null && mRecyclerView.isFocused(); 5886 } 5887 5888 /** 5889 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 5890 * 5891 * @return true if the RecyclerView has or contains focus 5892 * @see View#hasFocus() 5893 */ 5894 public boolean hasFocus() { 5895 return mRecyclerView != null && mRecyclerView.hasFocus(); 5896 } 5897 5898 /** 5899 * Returns the item View which has or contains focus. 5900 * 5901 * @return A direct child of RecyclerView which has focus or contains the focused child. 5902 */ 5903 public View getFocusedChild() { 5904 if (mRecyclerView == null) { 5905 return null; 5906 } 5907 final View focused = mRecyclerView.getFocusedChild(); 5908 if (focused == null || mChildHelper.isHidden(focused)) { 5909 return null; 5910 } 5911 return focused; 5912 } 5913 5914 /** 5915 * Returns the number of items in the adapter bound to the parent RecyclerView. 5916 * <p> 5917 * Note that this number is not necessarily equal to {@link State#getItemCount()}. In 5918 * methods where State is available, you should use {@link State#getItemCount()} instead. 5919 * For more details, check the documentation for {@link State#getItemCount()}. 5920 * 5921 * @return The number of items in the bound adapter 5922 * @see State#getItemCount() 5923 */ 5924 public int getItemCount() { 5925 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 5926 return a != null ? a.getItemCount() : 0; 5927 } 5928 5929 /** 5930 * Offset all child views attached to the parent RecyclerView by dx pixels along 5931 * the horizontal axis. 5932 * 5933 * @param dx Pixels to offset by 5934 */ 5935 public void offsetChildrenHorizontal(int dx) { 5936 if (mRecyclerView != null) { 5937 mRecyclerView.offsetChildrenHorizontal(dx); 5938 } 5939 } 5940 5941 /** 5942 * Offset all child views attached to the parent RecyclerView by dy pixels along 5943 * the vertical axis. 5944 * 5945 * @param dy Pixels to offset by 5946 */ 5947 public void offsetChildrenVertical(int dy) { 5948 if (mRecyclerView != null) { 5949 mRecyclerView.offsetChildrenVertical(dy); 5950 } 5951 } 5952 5953 /** 5954 * Flags a view so that it will not be scrapped or recycled. 5955 * <p> 5956 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and 5957 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child 5958 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not 5959 * ignore the child. 5960 * <p> 5961 * Before this child can be recycled again, you have to call 5962 * {@link #stopIgnoringView(View)}. 5963 * <p> 5964 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 5965 * 5966 * @param view View to ignore. 5967 * @see #stopIgnoringView(View) 5968 */ 5969 public void ignoreView(View view) { 5970 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { 5971 // checking this because calling this method on a recycled or detached view may 5972 // cause loss of state. 5973 throw new IllegalArgumentException("View should be fully attached to be ignored"); 5974 } 5975 final ViewHolder vh = getChildViewHolderInt(view); 5976 vh.addFlags(ViewHolder.FLAG_IGNORE); 5977 mRecyclerView.mState.onViewIgnored(vh); 5978 } 5979 5980 /** 5981 * View can be scrapped and recycled again. 5982 * <p> 5983 * Note that calling this method removes all information in the view holder. 5984 * <p> 5985 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 5986 * 5987 * @param view View to ignore. 5988 */ 5989 public void stopIgnoringView(View view) { 5990 final ViewHolder vh = getChildViewHolderInt(view); 5991 vh.stopIgnoring(); 5992 vh.resetInternal(); 5993 vh.addFlags(ViewHolder.FLAG_INVALID); 5994 } 5995 5996 /** 5997 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 5998 * into the given Recycler. The Recycler may prefer to reuse scrap views before 5999 * other views that were previously recycled. 6000 * 6001 * @param recycler Recycler to scrap views into 6002 */ 6003 public void detachAndScrapAttachedViews(Recycler recycler) { 6004 final int childCount = getChildCount(); 6005 for (int i = childCount - 1; i >= 0; i--) { 6006 final View v = getChildAt(i); 6007 scrapOrRecycleView(recycler, i, v); 6008 } 6009 } 6010 6011 private void scrapOrRecycleView(Recycler recycler, int index, View view) { 6012 final ViewHolder viewHolder = getChildViewHolderInt(view); 6013 if (viewHolder.shouldIgnore()) { 6014 if (DEBUG) { 6015 Log.d(TAG, "ignoring view " + viewHolder); 6016 } 6017 return; 6018 } 6019 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() && 6020 !mRecyclerView.mAdapter.hasStableIds()) { 6021 removeViewAt(index); 6022 recycler.recycleViewHolderInternal(viewHolder); 6023 } else { 6024 detachViewAt(index); 6025 recycler.scrapView(view); 6026 } 6027 } 6028 6029 /** 6030 * Recycles the scrapped views. 6031 * <p> 6032 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is 6033 * the expected behavior if scrapped views are used for animations. Otherwise, we need to 6034 * call remove and invalidate RecyclerView to ensure UI update. 6035 * 6036 * @param recycler Recycler 6037 */ 6038 void removeAndRecycleScrapInt(Recycler recycler) { 6039 final int scrapCount = recycler.getScrapCount(); 6040 // Loop backward, recycler might be changed by removeDetachedView() 6041 for (int i = scrapCount - 1; i >= 0; i--) { 6042 final View scrap = recycler.getScrapViewAt(i); 6043 final ViewHolder vh = getChildViewHolderInt(scrap); 6044 if (vh.shouldIgnore()) { 6045 continue; 6046 } 6047 if (vh.isTmpDetached()) { 6048 mRecyclerView.removeDetachedView(scrap, false); 6049 } 6050 recycler.quickRecycleScrapView(scrap); 6051 } 6052 recycler.clearScrap(); 6053 if (scrapCount > 0) { 6054 mRecyclerView.invalidate(); 6055 } 6056 } 6057 6058 6059 /** 6060 * Measure a child view using standard measurement policy, taking the padding 6061 * of the parent RecyclerView and any added item decorations into account. 6062 * 6063 * <p>If the RecyclerView can be scrolled in either dimension the caller may 6064 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 6065 * 6066 * @param child Child view to measure 6067 * @param widthUsed Width in pixels currently consumed by other views, if relevant 6068 * @param heightUsed Height in pixels currently consumed by other views, if relevant 6069 */ 6070 public void measureChild(View child, int widthUsed, int heightUsed) { 6071 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 6072 6073 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 6074 widthUsed += insets.left + insets.right; 6075 heightUsed += insets.top + insets.bottom; 6076 6077 final int widthSpec = getChildMeasureSpec(getWidth(), 6078 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 6079 canScrollHorizontally()); 6080 final int heightSpec = getChildMeasureSpec(getHeight(), 6081 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 6082 canScrollVertically()); 6083 child.measure(widthSpec, heightSpec); 6084 } 6085 6086 /** 6087 * Measure a child view using standard measurement policy, taking the padding 6088 * of the parent RecyclerView, any added item decorations and the child margins 6089 * into account. 6090 * 6091 * <p>If the RecyclerView can be scrolled in either dimension the caller may 6092 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 6093 * 6094 * @param child Child view to measure 6095 * @param widthUsed Width in pixels currently consumed by other views, if relevant 6096 * @param heightUsed Height in pixels currently consumed by other views, if relevant 6097 */ 6098 public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { 6099 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 6100 6101 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 6102 widthUsed += insets.left + insets.right; 6103 heightUsed += insets.top + insets.bottom; 6104 6105 final int widthSpec = getChildMeasureSpec(getWidth(), 6106 getPaddingLeft() + getPaddingRight() + 6107 lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 6108 canScrollHorizontally()); 6109 final int heightSpec = getChildMeasureSpec(getHeight(), 6110 getPaddingTop() + getPaddingBottom() + 6111 lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 6112 canScrollVertically()); 6113 child.measure(widthSpec, heightSpec); 6114 } 6115 6116 /** 6117 * Calculate a MeasureSpec value for measuring a child view in one dimension. 6118 * 6119 * @param parentSize Size of the parent view where the child will be placed 6120 * @param padding Total space currently consumed by other elements of parent 6121 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 6122 * Generally obtained from the child view's LayoutParams 6123 * @param canScroll true if the parent RecyclerView can scroll in this dimension 6124 * 6125 * @return a MeasureSpec value for the child view 6126 */ 6127 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 6128 boolean canScroll) { 6129 int size = Math.max(0, parentSize - padding); 6130 int resultSize = 0; 6131 int resultMode = 0; 6132 6133 if (canScroll) { 6134 if (childDimension >= 0) { 6135 resultSize = childDimension; 6136 resultMode = MeasureSpec.EXACTLY; 6137 } else { 6138 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 6139 // instead using UNSPECIFIED. 6140 resultSize = 0; 6141 resultMode = MeasureSpec.UNSPECIFIED; 6142 } 6143 } else { 6144 if (childDimension >= 0) { 6145 resultSize = childDimension; 6146 resultMode = MeasureSpec.EXACTLY; 6147 } else if (childDimension == LayoutParams.FILL_PARENT) { 6148 resultSize = size; 6149 resultMode = MeasureSpec.EXACTLY; 6150 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 6151 resultSize = size; 6152 resultMode = MeasureSpec.AT_MOST; 6153 } 6154 } 6155 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 6156 } 6157 6158 /** 6159 * Returns the measured width of the given child, plus the additional size of 6160 * any insets applied by {@link ItemDecoration ItemDecorations}. 6161 * 6162 * @param child Child view to query 6163 * @return child's measured width plus <code>ItemDecoration</code> insets 6164 * 6165 * @see View#getMeasuredWidth() 6166 */ 6167 public int getDecoratedMeasuredWidth(View child) { 6168 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 6169 return child.getMeasuredWidth() + insets.left + insets.right; 6170 } 6171 6172 /** 6173 * Returns the measured height of the given child, plus the additional size of 6174 * any insets applied by {@link ItemDecoration ItemDecorations}. 6175 * 6176 * @param child Child view to query 6177 * @return child's measured height plus <code>ItemDecoration</code> insets 6178 * 6179 * @see View#getMeasuredHeight() 6180 */ 6181 public int getDecoratedMeasuredHeight(View child) { 6182 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 6183 return child.getMeasuredHeight() + insets.top + insets.bottom; 6184 } 6185 6186 /** 6187 * Lay out the given child view within the RecyclerView using coordinates that 6188 * include any current {@link ItemDecoration ItemDecorations}. 6189 * 6190 * <p>LayoutManagers should prefer working in sizes and coordinates that include 6191 * item decoration insets whenever possible. This allows the LayoutManager to effectively 6192 * ignore decoration insets within measurement and layout code. See the following 6193 * methods:</p> 6194 * <ul> 6195 * <li>{@link #measureChild(View, int, int)}</li> 6196 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 6197 * <li>{@link #getDecoratedLeft(View)}</li> 6198 * <li>{@link #getDecoratedTop(View)}</li> 6199 * <li>{@link #getDecoratedRight(View)}</li> 6200 * <li>{@link #getDecoratedBottom(View)}</li> 6201 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 6202 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 6203 * </ul> 6204 * 6205 * @param child Child to lay out 6206 * @param left Left edge, with item decoration insets included 6207 * @param top Top edge, with item decoration insets included 6208 * @param right Right edge, with item decoration insets included 6209 * @param bottom Bottom edge, with item decoration insets included 6210 * 6211 * @see View#layout(int, int, int, int) 6212 */ 6213 public void layoutDecorated(View child, int left, int top, int right, int bottom) { 6214 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 6215 child.layout(left + insets.left, top + insets.top, right - insets.right, 6216 bottom - insets.bottom); 6217 } 6218 6219 /** 6220 * Returns the left edge of the given child view within its parent, offset by any applied 6221 * {@link ItemDecoration ItemDecorations}. 6222 * 6223 * @param child Child to query 6224 * @return Child left edge with offsets applied 6225 * @see #getLeftDecorationWidth(View) 6226 */ 6227 public int getDecoratedLeft(View child) { 6228 return child.getLeft() - getLeftDecorationWidth(child); 6229 } 6230 6231 /** 6232 * Returns the top edge of the given child view within its parent, offset by any applied 6233 * {@link ItemDecoration ItemDecorations}. 6234 * 6235 * @param child Child to query 6236 * @return Child top edge with offsets applied 6237 * @see #getTopDecorationHeight(View) 6238 */ 6239 public int getDecoratedTop(View child) { 6240 return child.getTop() - getTopDecorationHeight(child); 6241 } 6242 6243 /** 6244 * Returns the right edge of the given child view within its parent, offset by any applied 6245 * {@link ItemDecoration ItemDecorations}. 6246 * 6247 * @param child Child to query 6248 * @return Child right edge with offsets applied 6249 * @see #getRightDecorationWidth(View) 6250 */ 6251 public int getDecoratedRight(View child) { 6252 return child.getRight() + getRightDecorationWidth(child); 6253 } 6254 6255 /** 6256 * Returns the bottom edge of the given child view within its parent, offset by any applied 6257 * {@link ItemDecoration ItemDecorations}. 6258 * 6259 * @param child Child to query 6260 * @return Child bottom edge with offsets applied 6261 * @see #getBottomDecorationHeight(View) 6262 */ 6263 public int getDecoratedBottom(View child) { 6264 return child.getBottom() + getBottomDecorationHeight(child); 6265 } 6266 6267 /** 6268 * Calculates the item decor insets applied to the given child and updates the provided 6269 * Rect instance with the inset values. 6270 * <ul> 6271 * <li>The Rect's left is set to the total width of left decorations.</li> 6272 * <li>The Rect's top is set to the total height of top decorations.</li> 6273 * <li>The Rect's right is set to the total width of right decorations.</li> 6274 * <li>The Rect's bottom is set to total height of bottom decorations.</li> 6275 * </ul> 6276 * <p> 6277 * Note that item decorations are automatically calculated when one of the LayoutManager's 6278 * measure child methods is called. If you need to measure the child with custom specs via 6279 * {@link View#measure(int, int)}, you can use this method to get decorations. 6280 * 6281 * @param child The child view whose decorations should be calculated 6282 * @param outRect The Rect to hold result values 6283 */ 6284 public void calculateItemDecorationsForChild(View child, Rect outRect) { 6285 if (mRecyclerView == null) { 6286 outRect.set(0, 0, 0, 0); 6287 return; 6288 } 6289 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 6290 outRect.set(insets); 6291 } 6292 6293 /** 6294 * Returns the total height of item decorations applied to child's top. 6295 * <p> 6296 * Note that this value is not updated until the View is measured or 6297 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6298 * 6299 * @param child Child to query 6300 * @return The total height of item decorations applied to the child's top. 6301 * @see #getDecoratedTop(View) 6302 * @see #calculateItemDecorationsForChild(View, Rect) 6303 */ 6304 public int getTopDecorationHeight(View child) { 6305 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; 6306 } 6307 6308 /** 6309 * Returns the total height of item decorations applied to child's bottom. 6310 * <p> 6311 * Note that this value is not updated until the View is measured or 6312 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6313 * 6314 * @param child Child to query 6315 * @return The total height of item decorations applied to the child's bottom. 6316 * @see #getDecoratedBottom(View) 6317 * @see #calculateItemDecorationsForChild(View, Rect) 6318 */ 6319 public int getBottomDecorationHeight(View child) { 6320 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; 6321 } 6322 6323 /** 6324 * Returns the total width of item decorations applied to child's left. 6325 * <p> 6326 * Note that this value is not updated until the View is measured or 6327 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6328 * 6329 * @param child Child to query 6330 * @return The total width of item decorations applied to the child's left. 6331 * @see #getDecoratedLeft(View) 6332 * @see #calculateItemDecorationsForChild(View, Rect) 6333 */ 6334 public int getLeftDecorationWidth(View child) { 6335 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; 6336 } 6337 6338 /** 6339 * Returns the total width of item decorations applied to child's right. 6340 * <p> 6341 * Note that this value is not updated until the View is measured or 6342 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 6343 * 6344 * @param child Child to query 6345 * @return The total width of item decorations applied to the child's right. 6346 * @see #getDecoratedRight(View) 6347 * @see #calculateItemDecorationsForChild(View, Rect) 6348 */ 6349 public int getRightDecorationWidth(View child) { 6350 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; 6351 } 6352 6353 /** 6354 * Called when searching for a focusable view in the given direction has failed 6355 * for the current content of the RecyclerView. 6356 * 6357 * <p>This is the LayoutManager's opportunity to populate views in the given direction 6358 * to fulfill the request if it can. The LayoutManager should attach and return 6359 * the view to be focused. The default implementation returns null.</p> 6360 * 6361 * @param focused The currently focused view 6362 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6363 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6364 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6365 * or 0 for not applicable 6366 * @param recycler The recycler to use for obtaining views for currently offscreen items 6367 * @param state Transient state of RecyclerView 6368 * @return The chosen view to be focused 6369 */ 6370 @Nullable 6371 public View onFocusSearchFailed(View focused, int direction, Recycler recycler, 6372 State state) { 6373 return null; 6374 } 6375 6376 /** 6377 * This method gives a LayoutManager an opportunity to intercept the initial focus search 6378 * before the default behavior of {@link FocusFinder} is used. If this method returns 6379 * null FocusFinder will attempt to find a focusable child view. If it fails 6380 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} 6381 * will be called to give the LayoutManager an opportunity to add new views for items 6382 * that did not have attached views representing them. The LayoutManager should not add 6383 * or remove views from this method. 6384 * 6385 * @param focused The currently focused view 6386 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6387 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6388 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6389 * @return A descendant view to focus or null to fall back to default behavior. 6390 * The default implementation returns null. 6391 */ 6392 public View onInterceptFocusSearch(View focused, int direction) { 6393 return null; 6394 } 6395 6396 /** 6397 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 6398 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 6399 * android.graphics.Rect, boolean)} for more details. 6400 * 6401 * <p>The base implementation will attempt to perform a standard programmatic scroll 6402 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 6403 * 6404 * @param child The direct child making the request. 6405 * @param rect The rectangle in the child's coordinates the child 6406 * wishes to be on the screen. 6407 * @param immediate True to forbid animated or delayed scrolling, 6408 * false otherwise 6409 * @return Whether the group scrolled to handle the operation 6410 */ 6411 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, 6412 boolean immediate) { 6413 final int parentLeft = getPaddingLeft(); 6414 final int parentTop = getPaddingTop(); 6415 final int parentRight = getWidth() - getPaddingRight(); 6416 final int parentBottom = getHeight() - getPaddingBottom(); 6417 final int childLeft = child.getLeft() + rect.left; 6418 final int childTop = child.getTop() + rect.top; 6419 final int childRight = childLeft + rect.width(); 6420 final int childBottom = childTop + rect.height(); 6421 6422 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 6423 final int offScreenTop = Math.min(0, childTop - parentTop); 6424 final int offScreenRight = Math.max(0, childRight - parentRight); 6425 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 6426 6427 // Favor the "start" layout direction over the end when bringing one side or the other 6428 // of a large rect into view. If we decide to bring in end because start is already 6429 // visible, limit the scroll such that start won't go out of bounds. 6430 final int dx; 6431 if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) { 6432 dx = offScreenRight != 0 ? offScreenRight 6433 : Math.max(offScreenLeft, childRight - parentRight); 6434 } else { 6435 dx = offScreenLeft != 0 ? offScreenLeft 6436 : Math.min(childLeft - parentLeft, offScreenRight); 6437 } 6438 6439 // Favor bringing the top into view over the bottom. If top is already visible and 6440 // we should scroll to make bottom visible, make sure top does not go out of bounds. 6441 final int dy = offScreenTop != 0 ? offScreenTop 6442 : Math.min(childTop - parentTop, offScreenBottom); 6443 6444 if (dx != 0 || dy != 0) { 6445 if (immediate) { 6446 parent.scrollBy(dx, dy); 6447 } else { 6448 parent.smoothScrollBy(dx, dy); 6449 } 6450 return true; 6451 } 6452 return false; 6453 } 6454 6455 /** 6456 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} 6457 */ 6458 @Deprecated 6459 public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { 6460 // eat the request if we are in the middle of a scroll or layout 6461 return isSmoothScrolling() || parent.mRunningLayoutOrScroll; 6462 } 6463 6464 /** 6465 * Called when a descendant view of the RecyclerView requests focus. 6466 * 6467 * <p>A LayoutManager wishing to keep focused views aligned in a specific 6468 * portion of the view may implement that behavior in an override of this method.</p> 6469 * 6470 * <p>If the LayoutManager executes different behavior that should override the default 6471 * behavior of scrolling the focused child on screen instead of running alongside it, 6472 * this method should return true.</p> 6473 * 6474 * @param parent The RecyclerView hosting this LayoutManager 6475 * @param state Current state of RecyclerView 6476 * @param child Direct child of the RecyclerView containing the newly focused view 6477 * @param focused The newly focused view. This may be the same view as child or it may be 6478 * null 6479 * @return true if the default scroll behavior should be suppressed 6480 */ 6481 public boolean onRequestChildFocus(RecyclerView parent, State state, View child, 6482 View focused) { 6483 return onRequestChildFocus(parent, child, focused); 6484 } 6485 6486 /** 6487 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. 6488 * The LayoutManager may use this opportunity to clear caches and configure state such 6489 * that it can relayout appropriately with the new data and potentially new view types. 6490 * 6491 * <p>The default implementation removes all currently attached views.</p> 6492 * 6493 * @param oldAdapter The previous adapter instance. Will be null if there was previously no 6494 * adapter. 6495 * @param newAdapter The new adapter instance. Might be null if 6496 * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}. 6497 */ 6498 public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { 6499 } 6500 6501 /** 6502 * Called to populate focusable views within the RecyclerView. 6503 * 6504 * <p>The LayoutManager implementation should return <code>true</code> if the default 6505 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 6506 * suppressed.</p> 6507 * 6508 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 6509 * to fall back to the default ViewGroup behavior.</p> 6510 * 6511 * @param recyclerView The RecyclerView hosting this LayoutManager 6512 * @param views List of output views. This method should add valid focusable views 6513 * to this list. 6514 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 6515 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 6516 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 6517 * @param focusableMode The type of focusables to be added. 6518 * 6519 * @return true to suppress the default behavior, false to add default focusables after 6520 * this method returns. 6521 * 6522 * @see #FOCUSABLES_ALL 6523 * @see #FOCUSABLES_TOUCH_MODE 6524 */ 6525 public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, 6526 int direction, int focusableMode) { 6527 return false; 6528 } 6529 6530 /** 6531 * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving 6532 * detailed information on what has actually changed. 6533 * 6534 * @param recyclerView 6535 */ 6536 public void onItemsChanged(RecyclerView recyclerView) { 6537 } 6538 6539 /** 6540 * Called when items have been added to the adapter. The LayoutManager may choose to 6541 * requestLayout if the inserted items would require refreshing the currently visible set 6542 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 6543 * 6544 * @param recyclerView 6545 * @param positionStart 6546 * @param itemCount 6547 */ 6548 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 6549 } 6550 6551 /** 6552 * Called when items have been removed from the adapter. 6553 * 6554 * @param recyclerView 6555 * @param positionStart 6556 * @param itemCount 6557 */ 6558 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { 6559 } 6560 6561 /** 6562 * Called when items have been changed in the adapter. 6563 * 6564 * @param recyclerView 6565 * @param positionStart 6566 * @param itemCount 6567 */ 6568 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { 6569 } 6570 6571 /** 6572 * Called when an item is moved withing the adapter. 6573 * <p> 6574 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE 6575 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} 6576 * is called. 6577 * 6578 * @param recyclerView 6579 * @param from 6580 * @param to 6581 * @param itemCount 6582 */ 6583 public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { 6584 6585 } 6586 6587 6588 /** 6589 * <p>Override this method if you want to support scroll bars.</p> 6590 * 6591 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> 6592 * 6593 * <p>Default implementation returns 0.</p> 6594 * 6595 * @param state Current state of RecyclerView 6596 * @return The horizontal extent of the scrollbar's thumb 6597 * @see RecyclerView#computeHorizontalScrollExtent() 6598 */ 6599 public int computeHorizontalScrollExtent(State state) { 6600 return 0; 6601 } 6602 6603 /** 6604 * <p>Override this method if you want to support scroll bars.</p> 6605 * 6606 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> 6607 * 6608 * <p>Default implementation returns 0.</p> 6609 * 6610 * @param state Current State of RecyclerView where you can find total item count 6611 * @return The horizontal offset of the scrollbar's thumb 6612 * @see RecyclerView#computeHorizontalScrollOffset() 6613 */ 6614 public int computeHorizontalScrollOffset(State state) { 6615 return 0; 6616 } 6617 6618 /** 6619 * <p>Override this method if you want to support scroll bars.</p> 6620 * 6621 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> 6622 * 6623 * <p>Default implementation returns 0.</p> 6624 * 6625 * @param state Current State of RecyclerView where you can find total item count 6626 * @return The total horizontal range represented by the vertical scrollbar 6627 * @see RecyclerView#computeHorizontalScrollRange() 6628 */ 6629 public int computeHorizontalScrollRange(State state) { 6630 return 0; 6631 } 6632 6633 /** 6634 * <p>Override this method if you want to support scroll bars.</p> 6635 * 6636 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> 6637 * 6638 * <p>Default implementation returns 0.</p> 6639 * 6640 * @param state Current state of RecyclerView 6641 * @return The vertical extent of the scrollbar's thumb 6642 * @see RecyclerView#computeVerticalScrollExtent() 6643 */ 6644 public int computeVerticalScrollExtent(State state) { 6645 return 0; 6646 } 6647 6648 /** 6649 * <p>Override this method if you want to support scroll bars.</p> 6650 * 6651 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> 6652 * 6653 * <p>Default implementation returns 0.</p> 6654 * 6655 * @param state Current State of RecyclerView where you can find total item count 6656 * @return The vertical offset of the scrollbar's thumb 6657 * @see RecyclerView#computeVerticalScrollOffset() 6658 */ 6659 public int computeVerticalScrollOffset(State state) { 6660 return 0; 6661 } 6662 6663 /** 6664 * <p>Override this method if you want to support scroll bars.</p> 6665 * 6666 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> 6667 * 6668 * <p>Default implementation returns 0.</p> 6669 * 6670 * @param state Current State of RecyclerView where you can find total item count 6671 * @return The total vertical range represented by the vertical scrollbar 6672 * @see RecyclerView#computeVerticalScrollRange() 6673 */ 6674 public int computeVerticalScrollRange(State state) { 6675 return 0; 6676 } 6677 6678 /** 6679 * Measure the attached RecyclerView. Implementations must call 6680 * {@link #setMeasuredDimension(int, int)} before returning. 6681 * 6682 * <p>The default implementation will handle EXACTLY measurements and respect 6683 * the minimum width and height properties of the host RecyclerView if measured 6684 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView 6685 * will consume all available space.</p> 6686 * 6687 * @param recycler Recycler 6688 * @param state Transient state of RecyclerView 6689 * @param widthSpec Width {@link android.view.View.MeasureSpec} 6690 * @param heightSpec Height {@link android.view.View.MeasureSpec} 6691 */ 6692 public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { 6693 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 6694 } 6695 6696 /** 6697 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the 6698 * host RecyclerView. 6699 * 6700 * @param widthSize Measured width 6701 * @param heightSize Measured height 6702 */ 6703 public void setMeasuredDimension(int widthSize, int heightSize) { 6704 mRecyclerView.setMeasuredDimension(widthSize, heightSize); 6705 } 6706 6707 /** 6708 * @return The host RecyclerView's {@link View#getMinimumWidth()} 6709 */ 6710 public int getMinimumWidth() { 6711 return ViewCompat.getMinimumWidth(mRecyclerView); 6712 } 6713 6714 /** 6715 * @return The host RecyclerView's {@link View#getMinimumHeight()} 6716 */ 6717 public int getMinimumHeight() { 6718 return ViewCompat.getMinimumHeight(mRecyclerView); 6719 } 6720 /** 6721 * <p>Called when the LayoutManager should save its state. This is a good time to save your 6722 * scroll position, configuration and anything else that may be required to restore the same 6723 * layout state if the LayoutManager is recreated.</p> 6724 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and 6725 * restore. This will let you share information between your LayoutManagers but it is also 6726 * your responsibility to make sure they use the same parcelable class.</p> 6727 * 6728 * @return Necessary information for LayoutManager to be able to restore its state 6729 */ 6730 public Parcelable onSaveInstanceState() { 6731 return null; 6732 } 6733 6734 6735 public void onRestoreInstanceState(Parcelable state) { 6736 6737 } 6738 6739 void stopSmoothScroller() { 6740 if (mSmoothScroller != null) { 6741 mSmoothScroller.stop(); 6742 } 6743 } 6744 6745 private void onSmoothScrollerStopped(SmoothScroller smoothScroller) { 6746 if (mSmoothScroller == smoothScroller) { 6747 mSmoothScroller = null; 6748 } 6749 } 6750 6751 /** 6752 * RecyclerView calls this method to notify LayoutManager that scroll state has changed. 6753 * 6754 * @param state The new scroll state for RecyclerView 6755 */ 6756 public void onScrollStateChanged(int state) { 6757 } 6758 6759 /** 6760 * Removes all views and recycles them using the given recycler. 6761 * <p> 6762 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. 6763 * <p> 6764 * If a View is marked as "ignored", it is not removed nor recycled. 6765 * 6766 * @param recycler Recycler to use to recycle children 6767 * @see #removeAndRecycleView(View, Recycler) 6768 * @see #removeAndRecycleViewAt(int, Recycler) 6769 * @see #ignoreView(View) 6770 */ 6771 public void removeAndRecycleAllViews(Recycler recycler) { 6772 for (int i = getChildCount() - 1; i >= 0; i--) { 6773 final View view = getChildAt(i); 6774 if (!getChildViewHolderInt(view).shouldIgnore()) { 6775 removeAndRecycleViewAt(i, recycler); 6776 } 6777 } 6778 } 6779 6780 // called by accessibility delegate 6781 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 6782 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, 6783 info); 6784 } 6785 6786 /** 6787 * Called by the AccessibilityDelegate when the information about the current layout should 6788 * be populated. 6789 * <p> 6790 * Default implementation adds a {@link 6791 * android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. 6792 * <p> 6793 * You should override 6794 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 6795 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 6796 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and 6797 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for 6798 * more accurate accessibility information. 6799 * 6800 * @param recycler The Recycler that can be used to convert view positions into adapter 6801 * positions 6802 * @param state The current state of RecyclerView 6803 * @param info The info that should be filled by the LayoutManager 6804 * @see View#onInitializeAccessibilityNodeInfo( 6805 *android.view.accessibility.AccessibilityNodeInfo) 6806 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6807 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6808 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) 6809 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) 6810 */ 6811 public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state, 6812 AccessibilityNodeInfoCompat info) { 6813 if (ViewCompat.canScrollVertically(mRecyclerView, -1) || 6814 ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { 6815 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 6816 info.setScrollable(true); 6817 } 6818 if (ViewCompat.canScrollVertically(mRecyclerView, 1) || 6819 ViewCompat.canScrollHorizontally(mRecyclerView, 1)) { 6820 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 6821 info.setScrollable(true); 6822 } 6823 final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo 6824 = AccessibilityNodeInfoCompat.CollectionInfoCompat 6825 .obtain(getRowCountForAccessibility(recycler, state), 6826 getColumnCountForAccessibility(recycler, state), 6827 isLayoutHierarchical(recycler, state), 6828 getSelectionModeForAccessibility(recycler, state)); 6829 info.setCollectionInfo(collectionInfo); 6830 } 6831 6832 // called by accessibility delegate 6833 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 6834 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); 6835 } 6836 6837 /** 6838 * Called by the accessibility delegate to initialize an accessibility event. 6839 * <p> 6840 * Default implementation adds item count and scroll information to the event. 6841 * 6842 * @param recycler The Recycler that can be used to convert view positions into adapter 6843 * positions 6844 * @param state The current state of RecyclerView 6845 * @param event The event instance to initialize 6846 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) 6847 */ 6848 public void onInitializeAccessibilityEvent(Recycler recycler, State state, 6849 AccessibilityEvent event) { 6850 final AccessibilityRecordCompat record = AccessibilityEventCompat 6851 .asRecord(event); 6852 if (mRecyclerView == null || record == null) { 6853 return; 6854 } 6855 record.setScrollable(ViewCompat.canScrollVertically(mRecyclerView, 1) 6856 || ViewCompat.canScrollVertically(mRecyclerView, -1) 6857 || ViewCompat.canScrollHorizontally(mRecyclerView, -1) 6858 || ViewCompat.canScrollHorizontally(mRecyclerView, 1)); 6859 6860 if (mRecyclerView.mAdapter != null) { 6861 record.setItemCount(mRecyclerView.mAdapter.getItemCount()); 6862 } 6863 } 6864 6865 // called by accessibility delegate 6866 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { 6867 final ViewHolder vh = getChildViewHolderInt(host); 6868 // avoid trying to create accessibility node info for removed children 6869 if (vh != null && !vh.isRemoved()) { 6870 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, 6871 mRecyclerView.mState, host, info); 6872 } 6873 } 6874 6875 /** 6876 * Called by the AccessibilityDelegate when the accessibility information for a specific 6877 * item should be populated. 6878 * <p> 6879 * Default implementation adds basic positioning information about the item. 6880 * 6881 * @param recycler The Recycler that can be used to convert view positions into adapter 6882 * positions 6883 * @param state The current state of RecyclerView 6884 * @param host The child for which accessibility node info should be populated 6885 * @param info The info to fill out about the item 6886 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, 6887 * android.view.accessibility.AccessibilityNodeInfo) 6888 */ 6889 public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, 6890 View host, AccessibilityNodeInfoCompat info) { 6891 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; 6892 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; 6893 final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo 6894 = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, 6895 columnIndexGuess, 1, false, false); 6896 info.setCollectionItemInfo(itemInfo); 6897 } 6898 6899 /** 6900 * A LayoutManager can call this method to force RecyclerView to run simple animations in 6901 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data 6902 * change). 6903 * <p> 6904 * Note that, calling this method will not guarantee that RecyclerView will run animations 6905 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will 6906 * not run any animations but will still clear this flag after the layout is complete. 6907 * 6908 */ 6909 public void requestSimpleAnimationsInNextLayout() { 6910 mRequestedSimpleAnimations = true; 6911 } 6912 6913 /** 6914 * Returns the selection mode for accessibility. Should be 6915 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, 6916 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or 6917 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. 6918 * <p> 6919 * Default implementation returns 6920 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 6921 * 6922 * @param recycler The Recycler that can be used to convert view positions into adapter 6923 * positions 6924 * @param state The current state of RecyclerView 6925 * @return Selection mode for accessibility. Default implementation returns 6926 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 6927 */ 6928 public int getSelectionModeForAccessibility(Recycler recycler, State state) { 6929 return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; 6930 } 6931 6932 /** 6933 * Returns the number of rows for accessibility. 6934 * <p> 6935 * Default implementation returns the number of items in the adapter if LayoutManager 6936 * supports vertical scrolling or 1 if LayoutManager does not support vertical 6937 * scrolling. 6938 * 6939 * @param recycler The Recycler that can be used to convert view positions into adapter 6940 * positions 6941 * @param state The current state of RecyclerView 6942 * @return The number of rows in LayoutManager for accessibility. 6943 */ 6944 public int getRowCountForAccessibility(Recycler recycler, State state) { 6945 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 6946 return 1; 6947 } 6948 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; 6949 } 6950 6951 /** 6952 * Returns the number of columns for accessibility. 6953 * <p> 6954 * Default implementation returns the number of items in the adapter if LayoutManager 6955 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal 6956 * scrolling. 6957 * 6958 * @param recycler The Recycler that can be used to convert view positions into adapter 6959 * positions 6960 * @param state The current state of RecyclerView 6961 * @return The number of rows in LayoutManager for accessibility. 6962 */ 6963 public int getColumnCountForAccessibility(Recycler recycler, State state) { 6964 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 6965 return 1; 6966 } 6967 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; 6968 } 6969 6970 /** 6971 * Returns whether layout is hierarchical or not to be used for accessibility. 6972 * <p> 6973 * Default implementation returns false. 6974 * 6975 * @param recycler The Recycler that can be used to convert view positions into adapter 6976 * positions 6977 * @param state The current state of RecyclerView 6978 * @return True if layout is hierarchical. 6979 */ 6980 public boolean isLayoutHierarchical(Recycler recycler, State state) { 6981 return false; 6982 } 6983 6984 // called by accessibility delegate 6985 boolean performAccessibilityAction(int action, Bundle args) { 6986 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, 6987 action, args); 6988 } 6989 6990 /** 6991 * Called by AccessibilityDelegate when an action is requested from the RecyclerView. 6992 * 6993 * @param recycler The Recycler that can be used to convert view positions into adapter 6994 * positions 6995 * @param state The current state of RecyclerView 6996 * @param action The action to perform 6997 * @param args Optional action arguments 6998 * @see View#performAccessibilityAction(int, android.os.Bundle) 6999 */ 7000 public boolean performAccessibilityAction(Recycler recycler, State state, int action, 7001 Bundle args) { 7002 if (mRecyclerView == null) { 7003 return false; 7004 } 7005 int vScroll = 0, hScroll = 0; 7006 switch (action) { 7007 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 7008 if (ViewCompat.canScrollVertically(mRecyclerView, -1)) { 7009 vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); 7010 } 7011 if (ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { 7012 hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); 7013 } 7014 break; 7015 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 7016 if (ViewCompat.canScrollVertically(mRecyclerView, 1)) { 7017 vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); 7018 } 7019 if (ViewCompat.canScrollHorizontally(mRecyclerView, 1)) { 7020 hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); 7021 } 7022 break; 7023 } 7024 if (vScroll == 0 && hScroll == 0) { 7025 return false; 7026 } 7027 mRecyclerView.scrollBy(hScroll, vScroll); 7028 return true; 7029 } 7030 7031 // called by accessibility delegate 7032 boolean performAccessibilityActionForItem(View view, int action, Bundle args) { 7033 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, 7034 view, action, args); 7035 } 7036 7037 /** 7038 * Called by AccessibilityDelegate when an accessibility action is requested on one of the 7039 * children of LayoutManager. 7040 * <p> 7041 * Default implementation does not do anything. 7042 * 7043 * @param recycler The Recycler that can be used to convert view positions into adapter 7044 * positions 7045 * @param state The current state of RecyclerView 7046 * @param view The child view on which the action is performed 7047 * @param action The action to perform 7048 * @param args Optional action arguments 7049 * @return true if action is handled 7050 * @see View#performAccessibilityAction(int, android.os.Bundle) 7051 */ 7052 public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view, 7053 int action, Bundle args) { 7054 return false; 7055 } 7056 } 7057 7058 private void removeFromDisappearingList(View child) { 7059 mDisappearingViewsInLayoutPass.remove(child); 7060 } 7061 7062 private void addToDisappearingList(View child) { 7063 if (!mDisappearingViewsInLayoutPass.contains(child)) { 7064 mDisappearingViewsInLayoutPass.add(child); 7065 } 7066 } 7067 7068 /** 7069 * An ItemDecoration allows the application to add a special drawing and layout offset 7070 * to specific item views from the adapter's data set. This can be useful for drawing dividers 7071 * between items, highlights, visual grouping boundaries and more. 7072 * 7073 * <p>All ItemDecorations are drawn in the order they were added, before the item 7074 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} 7075 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, 7076 * RecyclerView.State)}.</p> 7077 */ 7078 public static abstract class ItemDecoration { 7079 /** 7080 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 7081 * Any content drawn by this method will be drawn before the item views are drawn, 7082 * and will thus appear underneath the views. 7083 * 7084 * @param c Canvas to draw into 7085 * @param parent RecyclerView this ItemDecoration is drawing into 7086 * @param state The current state of RecyclerView 7087 */ 7088 public void onDraw(Canvas c, RecyclerView parent, State state) { 7089 onDraw(c, parent); 7090 } 7091 7092 /** 7093 * @deprecated 7094 * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} 7095 */ 7096 @Deprecated 7097 public void onDraw(Canvas c, RecyclerView parent) { 7098 } 7099 7100 /** 7101 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 7102 * Any content drawn by this method will be drawn after the item views are drawn 7103 * and will thus appear over the views. 7104 * 7105 * @param c Canvas to draw into 7106 * @param parent RecyclerView this ItemDecoration is drawing into 7107 * @param state The current state of RecyclerView. 7108 */ 7109 public void onDrawOver(Canvas c, RecyclerView parent, State state) { 7110 onDrawOver(c, parent); 7111 } 7112 7113 /** 7114 * @deprecated 7115 * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} 7116 */ 7117 @Deprecated 7118 public void onDrawOver(Canvas c, RecyclerView parent) { 7119 } 7120 7121 7122 /** 7123 * @deprecated 7124 * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} 7125 */ 7126 @Deprecated 7127 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 7128 outRect.set(0, 0, 0, 0); 7129 } 7130 7131 /** 7132 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 7133 * the number of pixels that the item view should be inset by, similar to padding or margin. 7134 * The default implementation sets the bounds of outRect to 0 and returns. 7135 * 7136 * <p> 7137 * If this ItemDecoration does not affect the positioning of item views, it should set 7138 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 7139 * before returning. 7140 * 7141 * <p> 7142 * If you need to access Adapter for additional data, you can call 7143 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the 7144 * View. 7145 * 7146 * @param outRect Rect to receive the output. 7147 * @param view The child view to decorate 7148 * @param parent RecyclerView this ItemDecoration is decorating 7149 * @param state The current state of RecyclerView. 7150 */ 7151 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { 7152 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), 7153 parent); 7154 } 7155 } 7156 7157 /** 7158 * An OnItemTouchListener allows the application to intercept touch events in progress at the 7159 * view hierarchy level of the RecyclerView before those touch events are considered for 7160 * RecyclerView's own scrolling behavior. 7161 * 7162 * <p>This can be useful for applications that wish to implement various forms of gestural 7163 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 7164 * a touch interaction already in progress even if the RecyclerView is already handling that 7165 * gesture stream itself for the purposes of scrolling.</p> 7166 */ 7167 public interface OnItemTouchListener { 7168 /** 7169 * Silently observe and/or take over touch events sent to the RecyclerView 7170 * before they are handled by either the RecyclerView itself or its child views. 7171 * 7172 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 7173 * in the order in which each listener was added, before any other touch processing 7174 * by the RecyclerView itself or child views occurs.</p> 7175 * 7176 * @param e MotionEvent describing the touch event. All coordinates are in 7177 * the RecyclerView's coordinate system. 7178 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 7179 * to continue with the current behavior and continue observing future events in 7180 * the gesture. 7181 */ 7182 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e); 7183 7184 /** 7185 * Process a touch event as part of a gesture that was claimed by returning true from 7186 * a previous call to {@link #onInterceptTouchEvent}. 7187 * 7188 * @param e MotionEvent describing the touch event. All coordinates are in 7189 * the RecyclerView's coordinate system. 7190 */ 7191 public void onTouchEvent(RecyclerView rv, MotionEvent e); 7192 } 7193 7194 /** 7195 * An OnScrollListener can be set on a RecyclerView to receive messages 7196 * when a scrolling event has occurred on that RecyclerView. 7197 * 7198 * @see RecyclerView#setOnScrollListener(OnScrollListener) and 7199 * RecyclerView#addOnScrollListener(OnScrollListener) 7200 * 7201 * If you are planning to have several listeners at the same time, use 7202 * RecyclerView#addOnScrollListener. If there will be only one listener at the time and you 7203 * want your components to be able to easily replace the listener use 7204 * RecyclerView#setOnScrollListener. 7205 */ 7206 abstract static public class OnScrollListener { 7207 /** 7208 * Callback method to be invoked when RecyclerView's scroll state changes. 7209 * 7210 * @param recyclerView The RecyclerView whose scroll state has changed. 7211 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, 7212 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 7213 */ 7214 public void onScrollStateChanged(RecyclerView recyclerView, int newState){} 7215 7216 /** 7217 * Callback method to be invoked when the RecyclerView has been scrolled. This will be 7218 * called after the scroll has completed. 7219 * <p> 7220 * This callback will also be called if visible item range changes after a layout 7221 * calculation. In that case, dx and dy will be 0. 7222 * 7223 * @param recyclerView The RecyclerView which scrolled. 7224 * @param dx The amount of horizontal scroll. 7225 * @param dy The amount of vertical scroll. 7226 */ 7227 public void onScrolled(RecyclerView recyclerView, int dx, int dy){} 7228 } 7229 7230 /** 7231 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 7232 * a view is recycled. 7233 * 7234 * @see RecyclerView#setRecyclerListener(RecyclerListener) 7235 */ 7236 public interface RecyclerListener { 7237 7238 /** 7239 * This method is called whenever the view in the ViewHolder is recycled. 7240 * 7241 * RecyclerView calls this method right before clearing ViewHolder's internal data and 7242 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 7243 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 7244 * its adapter position. 7245 * 7246 * @param holder The ViewHolder containing the view that was recycled 7247 */ 7248 public void onViewRecycled(ViewHolder holder); 7249 } 7250 7251 /** 7252 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 7253 * 7254 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 7255 * potentially expensive {@link View#findViewById(int)} results.</p> 7256 * 7257 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 7258 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 7259 * their own custom ViewHolder implementations to store data that makes binding view contents 7260 * easier. Implementations should assume that individual item views will hold strong references 7261 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 7262 * strong references to extra off-screen item views for caching purposes</p> 7263 */ 7264 public static abstract class ViewHolder { 7265 public final View itemView; 7266 int mPosition = NO_POSITION; 7267 int mOldPosition = NO_POSITION; 7268 long mItemId = NO_ID; 7269 int mItemViewType = INVALID_TYPE; 7270 int mPreLayoutPosition = NO_POSITION; 7271 7272 // The item that this holder is shadowing during an item change event/animation 7273 ViewHolder mShadowedHolder = null; 7274 // The item that is shadowing this holder during an item change event/animation 7275 ViewHolder mShadowingHolder = null; 7276 7277 /** 7278 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 7279 * are all valid. 7280 */ 7281 static final int FLAG_BOUND = 1 << 0; 7282 7283 /** 7284 * The data this ViewHolder's view reflects is stale and needs to be rebound 7285 * by the adapter. mPosition and mItemId are consistent. 7286 */ 7287 static final int FLAG_UPDATE = 1 << 1; 7288 7289 /** 7290 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 7291 * are not to be trusted and may no longer match the item view type. 7292 * This ViewHolder must be fully rebound to different data. 7293 */ 7294 static final int FLAG_INVALID = 1 << 2; 7295 7296 /** 7297 * This ViewHolder points at data that represents an item previously removed from the 7298 * data set. Its view may still be used for things like outgoing animations. 7299 */ 7300 static final int FLAG_REMOVED = 1 << 3; 7301 7302 /** 7303 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 7304 * and is intended to keep views around during animations. 7305 */ 7306 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 7307 7308 /** 7309 * This ViewHolder is returned from scrap which means we are expecting an addView call 7310 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until 7311 * the end of the layout pass and then recycled by RecyclerView if it is not added back to 7312 * the RecyclerView. 7313 */ 7314 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; 7315 7316 /** 7317 * This ViewHolder's contents have changed. This flag is used as an indication that 7318 * change animations may be used, if supported by the ItemAnimator. 7319 */ 7320 static final int FLAG_CHANGED = 1 << 6; 7321 7322 /** 7323 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove 7324 * it unless LayoutManager is replaced. 7325 * It is still fully visible to the LayoutManager. 7326 */ 7327 static final int FLAG_IGNORE = 1 << 7; 7328 7329 /** 7330 * When the View is detached form the parent, we set this flag so that we can take correct 7331 * action when we need to remove it or add it back. 7332 */ 7333 static final int FLAG_TMP_DETACHED = 1 << 8; 7334 7335 /** 7336 * Set when we can no longer determine the adapter position of this ViewHolder until it is 7337 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is 7338 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon 7339 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is 7340 * re-calculated. 7341 */ 7342 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; 7343 7344 private int mFlags; 7345 7346 private int mIsRecyclableCount = 0; 7347 7348 // If non-null, view is currently considered scrap and may be reused for other data by the 7349 // scrap container. 7350 private Recycler mScrapContainer = null; 7351 7352 /** 7353 * Is set when VH is bound from the adapter and cleaned right before it is sent to 7354 * {@link RecycledViewPool}. 7355 */ 7356 RecyclerView mOwnerRecyclerView; 7357 7358 public ViewHolder(View itemView) { 7359 if (itemView == null) { 7360 throw new IllegalArgumentException("itemView may not be null"); 7361 } 7362 this.itemView = itemView; 7363 } 7364 7365 void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { 7366 addFlags(ViewHolder.FLAG_REMOVED); 7367 offsetPosition(offset, applyToPreLayout); 7368 mPosition = mNewPosition; 7369 } 7370 7371 void offsetPosition(int offset, boolean applyToPreLayout) { 7372 if (mOldPosition == NO_POSITION) { 7373 mOldPosition = mPosition; 7374 } 7375 if (mPreLayoutPosition == NO_POSITION) { 7376 mPreLayoutPosition = mPosition; 7377 } 7378 if (applyToPreLayout) { 7379 mPreLayoutPosition += offset; 7380 } 7381 mPosition += offset; 7382 if (itemView.getLayoutParams() != null) { 7383 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; 7384 } 7385 } 7386 7387 void clearOldPosition() { 7388 mOldPosition = NO_POSITION; 7389 mPreLayoutPosition = NO_POSITION; 7390 } 7391 7392 void saveOldPosition() { 7393 if (mOldPosition == NO_POSITION) { 7394 mOldPosition = mPosition; 7395 } 7396 } 7397 7398 boolean shouldIgnore() { 7399 return (mFlags & FLAG_IGNORE) != 0; 7400 } 7401 7402 /** 7403 * @deprecated This method is deprecated because its meaning is ambiguous due to the async 7404 * handling of adapter updates. Please use {@link #getLayoutPosition()} or 7405 * {@link #getAdapterPosition()} depending on your use case. 7406 * 7407 * @see #getLayoutPosition() 7408 * @see #getAdapterPosition() 7409 */ 7410 @Deprecated 7411 public final int getPosition() { 7412 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 7413 } 7414 7415 /** 7416 * Returns the position of the ViewHolder in terms of the latest layout pass. 7417 * <p> 7418 * This position is mostly used by RecyclerView components to be consistent while 7419 * RecyclerView lazily processes adapter updates. 7420 * <p> 7421 * For performance and animation reasons, RecyclerView batches all adapter updates until the 7422 * next layout pass. This may cause mismatches between the Adapter position of the item and 7423 * the position it had in the latest layout calculations. 7424 * <p> 7425 * LayoutManagers should always call this method while doing calculations based on item 7426 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, 7427 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position 7428 * of the item. 7429 * <p> 7430 * If LayoutManager needs to call an external method that requires the adapter position of 7431 * the item, it can use {@link #getAdapterPosition()} or 7432 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. 7433 * 7434 * @return Returns the adapter position of the ViewHolder in the latest layout pass. 7435 * @see #getAdapterPosition() 7436 */ 7437 public final int getLayoutPosition() { 7438 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 7439 } 7440 7441 /** 7442 * Returns the Adapter position of the item represented by this ViewHolder. 7443 * <p> 7444 * Note that this might be different than the {@link #getLayoutPosition()} if there are 7445 * pending adapter updates but a new layout pass has not happened yet. 7446 * <p> 7447 * RecyclerView does not handle any adapter updates until the next layout traversal. This 7448 * may create temporary inconsistencies between what user sees on the screen and what 7449 * adapter contents have. This inconsistency is not important since it will be less than 7450 * 16ms but it might be a problem if you want to use ViewHolder position to access the 7451 * adapter. Sometimes, you may need to get the exact adapter position to do 7452 * some actions in response to user events. In that case, you should use this method which 7453 * will calculate the Adapter position of the ViewHolder. 7454 * <p> 7455 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 7456 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 7457 * 7458 * @return The adapter position of the item if it still exists in the adapter. 7459 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 7460 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 7461 * layout pass or the ViewHolder has already been recycled. 7462 */ 7463 public final int getAdapterPosition() { 7464 if (mOwnerRecyclerView == null) { 7465 return NO_POSITION; 7466 } 7467 return mOwnerRecyclerView.getAdapterPositionFor(this); 7468 } 7469 7470 /** 7471 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders 7472 * to perform animations. 7473 * <p> 7474 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its 7475 * adapter index in the previous layout. 7476 * 7477 * @return The previous adapter index of the Item represented by this ViewHolder or 7478 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is 7479 * complete). 7480 */ 7481 public final int getOldPosition() { 7482 return mOldPosition; 7483 } 7484 7485 /** 7486 * Returns The itemId represented by this ViewHolder. 7487 * 7488 * @return The the item's id if adapter has stable ids, {@link RecyclerView#NO_ID} 7489 * otherwise 7490 */ 7491 public final long getItemId() { 7492 return mItemId; 7493 } 7494 7495 /** 7496 * @return The view type of this ViewHolder. 7497 */ 7498 public final int getItemViewType() { 7499 return mItemViewType; 7500 } 7501 7502 boolean isScrap() { 7503 return mScrapContainer != null; 7504 } 7505 7506 void unScrap() { 7507 mScrapContainer.unscrapView(this); 7508 } 7509 7510 boolean wasReturnedFromScrap() { 7511 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; 7512 } 7513 7514 void clearReturnedFromScrapFlag() { 7515 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; 7516 } 7517 7518 void clearTmpDetachFlag() { 7519 mFlags = mFlags & ~FLAG_TMP_DETACHED; 7520 } 7521 7522 void stopIgnoring() { 7523 mFlags = mFlags & ~FLAG_IGNORE; 7524 } 7525 7526 void setScrapContainer(Recycler recycler) { 7527 mScrapContainer = recycler; 7528 } 7529 7530 boolean isInvalid() { 7531 return (mFlags & FLAG_INVALID) != 0; 7532 } 7533 7534 boolean needsUpdate() { 7535 return (mFlags & FLAG_UPDATE) != 0; 7536 } 7537 7538 boolean isChanged() { 7539 return (mFlags & FLAG_CHANGED) != 0; 7540 } 7541 7542 boolean isBound() { 7543 return (mFlags & FLAG_BOUND) != 0; 7544 } 7545 7546 boolean isRemoved() { 7547 return (mFlags & FLAG_REMOVED) != 0; 7548 } 7549 7550 boolean hasAnyOfTheFlags(int flags) { 7551 return (mFlags & flags) != 0; 7552 } 7553 7554 boolean isTmpDetached() { 7555 return (mFlags & FLAG_TMP_DETACHED) != 0; 7556 } 7557 7558 boolean isAdapterPositionUnknown() { 7559 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); 7560 } 7561 7562 void setFlags(int flags, int mask) { 7563 mFlags = (mFlags & ~mask) | (flags & mask); 7564 } 7565 7566 void addFlags(int flags) { 7567 mFlags |= flags; 7568 } 7569 7570 void resetInternal() { 7571 mFlags = 0; 7572 mPosition = NO_POSITION; 7573 mOldPosition = NO_POSITION; 7574 mItemId = NO_ID; 7575 mPreLayoutPosition = NO_POSITION; 7576 mIsRecyclableCount = 0; 7577 mShadowedHolder = null; 7578 mShadowingHolder = null; 7579 } 7580 7581 @Override 7582 public String toString() { 7583 final StringBuilder sb = new StringBuilder("ViewHolder{" + 7584 Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId + 7585 ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); 7586 if (isScrap()) sb.append(" scrap"); 7587 if (isInvalid()) sb.append(" invalid"); 7588 if (!isBound()) sb.append(" unbound"); 7589 if (needsUpdate()) sb.append(" update"); 7590 if (isRemoved()) sb.append(" removed"); 7591 if (shouldIgnore()) sb.append(" ignored"); 7592 if (isChanged()) sb.append(" changed"); 7593 if (isTmpDetached()) sb.append(" tmpDetached"); 7594 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); 7595 if (isAdapterPositionUnknown()) sb.append("undefined adapter position"); 7596 7597 if (itemView.getParent() == null) sb.append(" no parent"); 7598 sb.append("}"); 7599 return sb.toString(); 7600 } 7601 7602 /** 7603 * Informs the recycler whether this item can be recycled. Views which are not 7604 * recyclable will not be reused for other items until setIsRecyclable() is 7605 * later set to true. Calls to setIsRecyclable() should always be paired (one 7606 * call to setIsRecyclabe(false) should always be matched with a later call to 7607 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally 7608 * reference-counted. 7609 * 7610 * @param recyclable Whether this item is available to be recycled. Default value 7611 * is true. 7612 */ 7613 public final void setIsRecyclable(boolean recyclable) { 7614 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; 7615 if (mIsRecyclableCount < 0) { 7616 mIsRecyclableCount = 0; 7617 if (DEBUG) { 7618 throw new RuntimeException("isRecyclable decremented below 0: " + 7619 "unmatched pair of setIsRecyable() calls for " + this); 7620 } 7621 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " + 7622 "unmatched pair of setIsRecyable() calls for " + this); 7623 } else if (!recyclable && mIsRecyclableCount == 1) { 7624 mFlags |= FLAG_NOT_RECYCLABLE; 7625 } else if (recyclable && mIsRecyclableCount == 0) { 7626 mFlags &= ~FLAG_NOT_RECYCLABLE; 7627 } 7628 if (DEBUG) { 7629 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); 7630 } 7631 } 7632 7633 /** 7634 * @see {@link #setIsRecyclable(boolean)} 7635 * 7636 * @return true if this item is available to be recycled, false otherwise. 7637 */ 7638 public final boolean isRecyclable() { 7639 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && 7640 !ViewCompat.hasTransientState(itemView); 7641 } 7642 7643 /** 7644 * Returns whether we have animations referring to this view holder or not. 7645 * This is similar to isRecyclable flag but does not check transient state. 7646 */ 7647 private boolean shouldBeKeptAsChild() { 7648 return (mFlags & FLAG_NOT_RECYCLABLE) != 0; 7649 } 7650 7651 /** 7652 * @return True if ViewHolder is not refenrenced by RecyclerView animations but has 7653 * transient state which will prevent it from being recycled. 7654 */ 7655 private boolean doesTransientStatePreventRecycling() { 7656 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); 7657 } 7658 } 7659 7660 private int getAdapterPositionFor(ViewHolder viewHolder) { 7661 if (viewHolder.hasAnyOfTheFlags( ViewHolder.FLAG_INVALID | 7662 ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) 7663 || !viewHolder.isBound()) { 7664 return RecyclerView.NO_POSITION; 7665 } 7666 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); 7667 } 7668 7669 /** 7670 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 7671 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 7672 * to create their own subclass of this <code>LayoutParams</code> class 7673 * to store any additional required per-child view metadata about the layout. 7674 */ 7675 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { 7676 ViewHolder mViewHolder; 7677 final Rect mDecorInsets = new Rect(); 7678 boolean mInsetsDirty = true; 7679 // Flag is set to true if the view is bound while it is detached from RV. 7680 // In this case, we need to manually call invalidate after view is added to guarantee that 7681 // invalidation is populated through the View hierarchy 7682 boolean mPendingInvalidate = false; 7683 7684 public LayoutParams(Context c, AttributeSet attrs) { 7685 super(c, attrs); 7686 } 7687 7688 public LayoutParams(int width, int height) { 7689 super(width, height); 7690 } 7691 7692 public LayoutParams(MarginLayoutParams source) { 7693 super(source); 7694 } 7695 7696 public LayoutParams(ViewGroup.LayoutParams source) { 7697 super(source); 7698 } 7699 7700 public LayoutParams(LayoutParams source) { 7701 super((ViewGroup.LayoutParams) source); 7702 } 7703 7704 /** 7705 * Returns true if the view this LayoutParams is attached to needs to have its content 7706 * updated from the corresponding adapter. 7707 * 7708 * @return true if the view should have its content updated 7709 */ 7710 public boolean viewNeedsUpdate() { 7711 return mViewHolder.needsUpdate(); 7712 } 7713 7714 /** 7715 * Returns true if the view this LayoutParams is attached to is now representing 7716 * potentially invalid data. A LayoutManager should scrap/recycle it. 7717 * 7718 * @return true if the view is invalid 7719 */ 7720 public boolean isViewInvalid() { 7721 return mViewHolder.isInvalid(); 7722 } 7723 7724 /** 7725 * Returns true if the adapter data item corresponding to the view this LayoutParams 7726 * is attached to has been removed from the data set. A LayoutManager may choose to 7727 * treat it differently in order to animate its outgoing or disappearing state. 7728 * 7729 * @return true if the item the view corresponds to was removed from the data set 7730 */ 7731 public boolean isItemRemoved() { 7732 return mViewHolder.isRemoved(); 7733 } 7734 7735 /** 7736 * Returns true if the adapter data item corresponding to the view this LayoutParams 7737 * is attached to has been changed in the data set. A LayoutManager may choose to 7738 * treat it differently in order to animate its changing state. 7739 * 7740 * @return true if the item the view corresponds to was changed in the data set 7741 */ 7742 public boolean isItemChanged() { 7743 return mViewHolder.isChanged(); 7744 } 7745 7746 /** 7747 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} 7748 */ 7749 public int getViewPosition() { 7750 return mViewHolder.getPosition(); 7751 } 7752 7753 /** 7754 * Returns the adapter position that the view this LayoutParams is attached to corresponds 7755 * to as of latest layout calculation. 7756 * 7757 * @return the adapter position this view as of latest layout pass 7758 */ 7759 public int getViewLayoutPosition() { 7760 return mViewHolder.getLayoutPosition(); 7761 } 7762 7763 /** 7764 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 7765 * corresponds to. 7766 * 7767 * @return the up-to-date adapter position this view. It may return 7768 * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or 7769 * its up-to-date position cannot be calculated. 7770 */ 7771 public int getViewAdapterPosition() { 7772 return mViewHolder.getAdapterPosition(); 7773 } 7774 } 7775 7776 /** 7777 * Observer base class for watching changes to an {@link Adapter}. 7778 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 7779 */ 7780 public static abstract class AdapterDataObserver { 7781 public void onChanged() { 7782 // Do nothing 7783 } 7784 7785 public void onItemRangeChanged(int positionStart, int itemCount) { 7786 // do nothing 7787 } 7788 7789 public void onItemRangeInserted(int positionStart, int itemCount) { 7790 // do nothing 7791 } 7792 7793 public void onItemRangeRemoved(int positionStart, int itemCount) { 7794 // do nothing 7795 } 7796 7797 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 7798 // do nothing 7799 } 7800 } 7801 7802 /** 7803 * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and 7804 * provides methods to trigger a programmatic scroll.</p> 7805 * 7806 * @see LinearSmoothScroller 7807 */ 7808 public static abstract class SmoothScroller { 7809 7810 private int mTargetPosition = RecyclerView.NO_POSITION; 7811 7812 private RecyclerView mRecyclerView; 7813 7814 private LayoutManager mLayoutManager; 7815 7816 private boolean mPendingInitialRun; 7817 7818 private boolean mRunning; 7819 7820 private View mTargetView; 7821 7822 private final Action mRecyclingAction; 7823 7824 public SmoothScroller() { 7825 mRecyclingAction = new Action(0, 0); 7826 } 7827 7828 /** 7829 * Starts a smooth scroll for the given target position. 7830 * <p>In each animation step, {@link RecyclerView} will check 7831 * for the target view and call either 7832 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 7833 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until 7834 * SmoothScroller is stopped.</p> 7835 * 7836 * <p>Note that if RecyclerView finds the target view, it will automatically stop the 7837 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will 7838 * stop calling SmoothScroller in each animation step.</p> 7839 */ 7840 void start(RecyclerView recyclerView, LayoutManager layoutManager) { 7841 mRecyclerView = recyclerView; 7842 mLayoutManager = layoutManager; 7843 if (mTargetPosition == RecyclerView.NO_POSITION) { 7844 throw new IllegalArgumentException("Invalid target position"); 7845 } 7846 mRecyclerView.mState.mTargetPosition = mTargetPosition; 7847 mRunning = true; 7848 mPendingInitialRun = true; 7849 mTargetView = findViewByPosition(getTargetPosition()); 7850 onStart(); 7851 mRecyclerView.mViewFlinger.postOnAnimation(); 7852 } 7853 7854 public void setTargetPosition(int targetPosition) { 7855 mTargetPosition = targetPosition; 7856 } 7857 7858 /** 7859 * @return The LayoutManager to which this SmoothScroller is attached 7860 */ 7861 public LayoutManager getLayoutManager() { 7862 return mLayoutManager; 7863 } 7864 7865 /** 7866 * Stops running the SmoothScroller in each animation callback. Note that this does not 7867 * cancel any existing {@link Action} updated by 7868 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 7869 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. 7870 */ 7871 final protected void stop() { 7872 if (!mRunning) { 7873 return; 7874 } 7875 onStop(); 7876 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; 7877 mTargetView = null; 7878 mTargetPosition = RecyclerView.NO_POSITION; 7879 mPendingInitialRun = false; 7880 mRunning = false; 7881 // trigger a cleanup 7882 mLayoutManager.onSmoothScrollerStopped(this); 7883 // clear references to avoid any potential leak by a custom smooth scroller 7884 mLayoutManager = null; 7885 mRecyclerView = null; 7886 } 7887 7888 /** 7889 * Returns true if SmoothScroller has been started but has not received the first 7890 * animation 7891 * callback yet. 7892 * 7893 * @return True if this SmoothScroller is waiting to start 7894 */ 7895 public boolean isPendingInitialRun() { 7896 return mPendingInitialRun; 7897 } 7898 7899 7900 /** 7901 * @return True if SmoothScroller is currently active 7902 */ 7903 public boolean isRunning() { 7904 return mRunning; 7905 } 7906 7907 /** 7908 * Returns the adapter position of the target item 7909 * 7910 * @return Adapter position of the target item or 7911 * {@link RecyclerView#NO_POSITION} if no target view is set. 7912 */ 7913 public int getTargetPosition() { 7914 return mTargetPosition; 7915 } 7916 7917 private void onAnimation(int dx, int dy) { 7918 if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) { 7919 stop(); 7920 } 7921 mPendingInitialRun = false; 7922 if (mTargetView != null) { 7923 // verify target position 7924 if (getChildPosition(mTargetView) == mTargetPosition) { 7925 onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction); 7926 mRecyclingAction.runIfNecessary(mRecyclerView); 7927 stop(); 7928 } else { 7929 Log.e(TAG, "Passed over target position while smooth scrolling."); 7930 mTargetView = null; 7931 } 7932 } 7933 if (mRunning) { 7934 onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction); 7935 mRecyclingAction.runIfNecessary(mRecyclerView); 7936 } 7937 } 7938 7939 /** 7940 * @see RecyclerView#getChildLayoutPosition(android.view.View) 7941 */ 7942 public int getChildPosition(View view) { 7943 return mRecyclerView.getChildLayoutPosition(view); 7944 } 7945 7946 /** 7947 * @see RecyclerView.LayoutManager#getChildCount() 7948 */ 7949 public int getChildCount() { 7950 return mRecyclerView.mLayout.getChildCount(); 7951 } 7952 7953 /** 7954 * @see RecyclerView.LayoutManager#findViewByPosition(int) 7955 */ 7956 public View findViewByPosition(int position) { 7957 return mRecyclerView.mLayout.findViewByPosition(position); 7958 } 7959 7960 /** 7961 * @see RecyclerView#scrollToPosition(int) 7962 */ 7963 public void instantScrollToPosition(int position) { 7964 mRecyclerView.scrollToPosition(position); 7965 } 7966 7967 protected void onChildAttachedToWindow(View child) { 7968 if (getChildPosition(child) == getTargetPosition()) { 7969 mTargetView = child; 7970 if (DEBUG) { 7971 Log.d(TAG, "smooth scroll target view has been attached"); 7972 } 7973 } 7974 } 7975 7976 /** 7977 * Normalizes the vector. 7978 * @param scrollVector The vector that points to the target scroll position 7979 */ 7980 protected void normalize(PointF scrollVector) { 7981 final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y * 7982 scrollVector.y); 7983 scrollVector.x /= magnitute; 7984 scrollVector.y /= magnitute; 7985 } 7986 7987 /** 7988 * Called when smooth scroll is started. This might be a good time to do setup. 7989 */ 7990 abstract protected void onStart(); 7991 7992 /** 7993 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. 7994 * @see #stop() 7995 */ 7996 abstract protected void onStop(); 7997 7998 /** 7999 * <p>RecyclerView will call this method each time it scrolls until it can find the target 8000 * position in the layout.</p> 8001 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the 8002 * provided {@link Action} to define the next scroll.</p> 8003 * 8004 * @param dx Last scroll amount horizontally 8005 * @param dy Last scroll amount verticaully 8006 * @param state Transient state of RecyclerView 8007 * @param action If you want to trigger a new smooth scroll and cancel the previous one, 8008 * update this object. 8009 */ 8010 abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action); 8011 8012 /** 8013 * Called when the target position is laid out. This is the last callback SmoothScroller 8014 * will receive and it should update the provided {@link Action} to define the scroll 8015 * details towards the target view. 8016 * @param targetView The view element which render the target position. 8017 * @param state Transient state of RecyclerView 8018 * @param action Action instance that you should update to define final scroll action 8019 * towards the targetView 8020 */ 8021 abstract protected void onTargetFound(View targetView, State state, Action action); 8022 8023 /** 8024 * Holds information about a smooth scroll request by a {@link SmoothScroller}. 8025 */ 8026 public static class Action { 8027 8028 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; 8029 8030 private int mDx; 8031 8032 private int mDy; 8033 8034 private int mDuration; 8035 8036 private Interpolator mInterpolator; 8037 8038 private boolean changed = false; 8039 8040 // we track this variable to inform custom implementer if they are updating the action 8041 // in every animation callback 8042 private int consecutiveUpdates = 0; 8043 8044 /** 8045 * @param dx Pixels to scroll horizontally 8046 * @param dy Pixels to scroll vertically 8047 */ 8048 public Action(int dx, int dy) { 8049 this(dx, dy, UNDEFINED_DURATION, null); 8050 } 8051 8052 /** 8053 * @param dx Pixels to scroll horizontally 8054 * @param dy Pixels to scroll vertically 8055 * @param duration Duration of the animation in milliseconds 8056 */ 8057 public Action(int dx, int dy, int duration) { 8058 this(dx, dy, duration, null); 8059 } 8060 8061 /** 8062 * @param dx Pixels to scroll horizontally 8063 * @param dy Pixels to scroll vertically 8064 * @param duration Duration of the animation in milliseconds 8065 * @param interpolator Interpolator to be used when calculating scroll position in each 8066 * animation step 8067 */ 8068 public Action(int dx, int dy, int duration, Interpolator interpolator) { 8069 mDx = dx; 8070 mDy = dy; 8071 mDuration = duration; 8072 mInterpolator = interpolator; 8073 } 8074 private void runIfNecessary(RecyclerView recyclerView) { 8075 if (changed) { 8076 validate(); 8077 if (mInterpolator == null) { 8078 if (mDuration == UNDEFINED_DURATION) { 8079 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); 8080 } else { 8081 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); 8082 } 8083 } else { 8084 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator); 8085 } 8086 consecutiveUpdates ++; 8087 if (consecutiveUpdates > 10) { 8088 // A new action is being set in every animation step. This looks like a bad 8089 // implementation. Inform developer. 8090 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" 8091 + " you are not changing it unless necessary"); 8092 } 8093 changed = false; 8094 } else { 8095 consecutiveUpdates = 0; 8096 } 8097 } 8098 8099 private void validate() { 8100 if (mInterpolator != null && mDuration < 1) { 8101 throw new IllegalStateException("If you provide an interpolator, you must" 8102 + " set a positive duration"); 8103 } else if (mDuration < 1) { 8104 throw new IllegalStateException("Scroll duration must be a positive number"); 8105 } 8106 } 8107 8108 public int getDx() { 8109 return mDx; 8110 } 8111 8112 public void setDx(int dx) { 8113 changed = true; 8114 mDx = dx; 8115 } 8116 8117 public int getDy() { 8118 return mDy; 8119 } 8120 8121 public void setDy(int dy) { 8122 changed = true; 8123 mDy = dy; 8124 } 8125 8126 public int getDuration() { 8127 return mDuration; 8128 } 8129 8130 public void setDuration(int duration) { 8131 changed = true; 8132 mDuration = duration; 8133 } 8134 8135 public Interpolator getInterpolator() { 8136 return mInterpolator; 8137 } 8138 8139 /** 8140 * Sets the interpolator to calculate scroll steps 8141 * @param interpolator The interpolator to use. If you specify an interpolator, you must 8142 * also set the duration. 8143 * @see #setDuration(int) 8144 */ 8145 public void setInterpolator(Interpolator interpolator) { 8146 changed = true; 8147 mInterpolator = interpolator; 8148 } 8149 8150 /** 8151 * Updates the action with given parameters. 8152 * @param dx Pixels to scroll horizontally 8153 * @param dy Pixels to scroll vertically 8154 * @param duration Duration of the animation in milliseconds 8155 * @param interpolator Interpolator to be used when calculating scroll position in each 8156 * animation step 8157 */ 8158 public void update(int dx, int dy, int duration, Interpolator interpolator) { 8159 mDx = dx; 8160 mDy = dy; 8161 mDuration = duration; 8162 mInterpolator = interpolator; 8163 changed = true; 8164 } 8165 } 8166 } 8167 8168 static class AdapterDataObservable extends Observable<AdapterDataObserver> { 8169 public boolean hasObservers() { 8170 return !mObservers.isEmpty(); 8171 } 8172 8173 public void notifyChanged() { 8174 // since onChanged() is implemented by the app, it could do anything, including 8175 // removing itself from {@link mObservers} - and that could cause problems if 8176 // an iterator is used on the ArrayList {@link mObservers}. 8177 // to avoid such problems, just march thru the list in the reverse order. 8178 for (int i = mObservers.size() - 1; i >= 0; i--) { 8179 mObservers.get(i).onChanged(); 8180 } 8181 } 8182 8183 public void notifyItemRangeChanged(int positionStart, int itemCount) { 8184 // since onItemRangeChanged() is implemented by the app, it could do anything, including 8185 // removing itself from {@link mObservers} - and that could cause problems if 8186 // an iterator is used on the ArrayList {@link mObservers}. 8187 // to avoid such problems, just march thru the list in the reverse order. 8188 for (int i = mObservers.size() - 1; i >= 0; i--) { 8189 mObservers.get(i).onItemRangeChanged(positionStart, itemCount); 8190 } 8191 } 8192 8193 public void notifyItemRangeInserted(int positionStart, int itemCount) { 8194 // since onItemRangeInserted() is implemented by the app, it could do anything, 8195 // including removing itself from {@link mObservers} - and that could cause problems if 8196 // an iterator is used on the ArrayList {@link mObservers}. 8197 // to avoid such problems, just march thru the list in the reverse order. 8198 for (int i = mObservers.size() - 1; i >= 0; i--) { 8199 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 8200 } 8201 } 8202 8203 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 8204 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 8205 // removing itself from {@link mObservers} - and that could cause problems if 8206 // an iterator is used on the ArrayList {@link mObservers}. 8207 // to avoid such problems, just march thru the list in the reverse order. 8208 for (int i = mObservers.size() - 1; i >= 0; i--) { 8209 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 8210 } 8211 } 8212 8213 public void notifyItemMoved(int fromPosition, int toPosition) { 8214 for (int i = mObservers.size() - 1; i >= 0; i--) { 8215 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); 8216 } 8217 } 8218 } 8219 8220 static class SavedState extends android.view.View.BaseSavedState { 8221 8222 Parcelable mLayoutState; 8223 8224 /** 8225 * called by CREATOR 8226 */ 8227 SavedState(Parcel in) { 8228 super(in); 8229 mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader()); 8230 } 8231 8232 /** 8233 * Called by onSaveInstanceState 8234 */ 8235 SavedState(Parcelable superState) { 8236 super(superState); 8237 } 8238 8239 @Override 8240 public void writeToParcel(Parcel dest, int flags) { 8241 super.writeToParcel(dest, flags); 8242 dest.writeParcelable(mLayoutState, 0); 8243 } 8244 8245 private void copyFrom(SavedState other) { 8246 mLayoutState = other.mLayoutState; 8247 } 8248 8249 public static final Parcelable.Creator<SavedState> CREATOR 8250 = new Parcelable.Creator<SavedState>() { 8251 @Override 8252 public SavedState createFromParcel(Parcel in) { 8253 return new SavedState(in); 8254 } 8255 8256 @Override 8257 public SavedState[] newArray(int size) { 8258 return new SavedState[size]; 8259 } 8260 }; 8261 } 8262 /** 8263 * <p>Contains useful information about the current RecyclerView state like target scroll 8264 * position or view focus. State object can also keep arbitrary data, identified by resource 8265 * ids.</p> 8266 * <p>Often times, RecyclerView components will need to pass information between each other. 8267 * To provide a well defined data bus between components, RecyclerView passes the same State 8268 * object to component callbacks and these components can use it to exchange data.</p> 8269 * <p>If you implement custom components, you can use State's put/get/remove methods to pass 8270 * data between your components without needing to manage their lifecycles.</p> 8271 */ 8272 public static class State { 8273 8274 private int mTargetPosition = RecyclerView.NO_POSITION; 8275 ArrayMap<ViewHolder, ItemHolderInfo> mPreLayoutHolderMap = 8276 new ArrayMap<ViewHolder, ItemHolderInfo>(); 8277 ArrayMap<ViewHolder, ItemHolderInfo> mPostLayoutHolderMap = 8278 new ArrayMap<ViewHolder, ItemHolderInfo>(); 8279 // nullable 8280 ArrayMap<Long, ViewHolder> mOldChangedHolders = new ArrayMap<Long, ViewHolder>(); 8281 8282 private SparseArray<Object> mData; 8283 8284 /** 8285 * Number of items adapter has. 8286 */ 8287 int mItemCount = 0; 8288 8289 /** 8290 * Number of items adapter had in the previous layout. 8291 */ 8292 private int mPreviousLayoutItemCount = 0; 8293 8294 /** 8295 * Number of items that were NOT laid out but has been deleted from the adapter after the 8296 * previous layout. 8297 */ 8298 private int mDeletedInvisibleItemCountSincePreviousLayout = 0; 8299 8300 private boolean mStructureChanged = false; 8301 8302 private boolean mInPreLayout = false; 8303 8304 private boolean mRunSimpleAnimations = false; 8305 8306 private boolean mRunPredictiveAnimations = false; 8307 8308 State reset() { 8309 mTargetPosition = RecyclerView.NO_POSITION; 8310 if (mData != null) { 8311 mData.clear(); 8312 } 8313 mItemCount = 0; 8314 mStructureChanged = false; 8315 return this; 8316 } 8317 8318 public boolean isPreLayout() { 8319 return mInPreLayout; 8320 } 8321 8322 /** 8323 * Returns whether RecyclerView will run predictive animations in this layout pass 8324 * or not. 8325 * 8326 * @return true if RecyclerView is calculating predictive animations to be run at the end 8327 * of the layout pass. 8328 */ 8329 public boolean willRunPredictiveAnimations() { 8330 return mRunPredictiveAnimations; 8331 } 8332 8333 /** 8334 * Returns whether RecyclerView will run simple animations in this layout pass 8335 * or not. 8336 * 8337 * @return true if RecyclerView is calculating simple animations to be run at the end of 8338 * the layout pass. 8339 */ 8340 public boolean willRunSimpleAnimations() { 8341 return mRunSimpleAnimations; 8342 } 8343 8344 /** 8345 * Removes the mapping from the specified id, if there was any. 8346 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to 8347 * preserve cross functionality and avoid conflicts. 8348 */ 8349 public void remove(int resourceId) { 8350 if (mData == null) { 8351 return; 8352 } 8353 mData.remove(resourceId); 8354 } 8355 8356 /** 8357 * Gets the Object mapped from the specified id, or <code>null</code> 8358 * if no such data exists. 8359 * 8360 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* 8361 * to 8362 * preserve cross functionality and avoid conflicts. 8363 */ 8364 public <T> T get(int resourceId) { 8365 if (mData == null) { 8366 return null; 8367 } 8368 return (T) mData.get(resourceId); 8369 } 8370 8371 /** 8372 * Adds a mapping from the specified id to the specified value, replacing the previous 8373 * mapping from the specified key if there was one. 8374 * 8375 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to 8376 * preserve cross functionality and avoid conflicts. 8377 * @param data The data you want to associate with the resourceId. 8378 */ 8379 public void put(int resourceId, Object data) { 8380 if (mData == null) { 8381 mData = new SparseArray<Object>(); 8382 } 8383 mData.put(resourceId, data); 8384 } 8385 8386 /** 8387 * If scroll is triggered to make a certain item visible, this value will return the 8388 * adapter index of that item. 8389 * @return Adapter index of the target item or 8390 * {@link RecyclerView#NO_POSITION} if there is no target 8391 * position. 8392 */ 8393 public int getTargetScrollPosition() { 8394 return mTargetPosition; 8395 } 8396 8397 /** 8398 * Returns if current scroll has a target position. 8399 * @return true if scroll is being triggered to make a certain position visible 8400 * @see #getTargetScrollPosition() 8401 */ 8402 public boolean hasTargetScrollPosition() { 8403 return mTargetPosition != RecyclerView.NO_POSITION; 8404 } 8405 8406 /** 8407 * @return true if the structure of the data set has changed since the last call to 8408 * onLayoutChildren, false otherwise 8409 */ 8410 public boolean didStructureChange() { 8411 return mStructureChanged; 8412 } 8413 8414 /** 8415 * Returns the total number of items that can be laid out. Note that this number is not 8416 * necessarily equal to the number of items in the adapter, so you should always use this 8417 * number for your position calculations and never access the adapter directly. 8418 * <p> 8419 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter 8420 * data changes on existing Views. These calculations are used to decide which animations 8421 * should be run. 8422 * <p> 8423 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to 8424 * present the correct state to LayoutManager in pre-layout pass. 8425 * <p> 8426 * For example, a newly added item is not included in pre-layout item count because 8427 * pre-layout reflects the contents of the adapter before the item is added. Behind the 8428 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that 8429 * LayoutManager does not know about the new item's existence in pre-layout. The item will 8430 * be available in second layout pass and will be included in the item count. Similar 8431 * adjustments are made for moved and removed items as well. 8432 * <p> 8433 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. 8434 * 8435 * @return The number of items currently available 8436 * @see LayoutManager#getItemCount() 8437 */ 8438 public int getItemCount() { 8439 return mInPreLayout ? 8440 (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) : 8441 mItemCount; 8442 } 8443 8444 void onViewRecycled(ViewHolder holder) { 8445 mPreLayoutHolderMap.remove(holder); 8446 mPostLayoutHolderMap.remove(holder); 8447 if (mOldChangedHolders != null) { 8448 removeFrom(mOldChangedHolders, holder); 8449 } 8450 // holder cannot be in new list. 8451 } 8452 8453 public void onViewIgnored(ViewHolder holder) { 8454 onViewRecycled(holder); 8455 } 8456 8457 private void removeFrom(ArrayMap<Long, ViewHolder> holderMap, ViewHolder holder) { 8458 for (int i = holderMap.size() - 1; i >= 0; i --) { 8459 if (holder == holderMap.valueAt(i)) { 8460 holderMap.removeAt(i); 8461 return; 8462 } 8463 } 8464 } 8465 8466 @Override 8467 public String toString() { 8468 return "State{" + 8469 "mTargetPosition=" + mTargetPosition + 8470 ", mPreLayoutHolderMap=" + mPreLayoutHolderMap + 8471 ", mPostLayoutHolderMap=" + mPostLayoutHolderMap + 8472 ", mData=" + mData + 8473 ", mItemCount=" + mItemCount + 8474 ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount + 8475 ", mDeletedInvisibleItemCountSincePreviousLayout=" 8476 + mDeletedInvisibleItemCountSincePreviousLayout + 8477 ", mStructureChanged=" + mStructureChanged + 8478 ", mInPreLayout=" + mInPreLayout + 8479 ", mRunSimpleAnimations=" + mRunSimpleAnimations + 8480 ", mRunPredictiveAnimations=" + mRunPredictiveAnimations + 8481 '}'; 8482 } 8483 } 8484 8485 /** 8486 * Internal listener that manages items after animations finish. This is how items are 8487 * retained (not recycled) during animations, but allowed to be recycled afterwards. 8488 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() 8489 * method on the animator's listener when it is done animating any item. 8490 */ 8491 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { 8492 8493 @Override 8494 public void onRemoveFinished(ViewHolder item) { 8495 item.setIsRecyclable(true); 8496 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { 8497 removeDetachedView(item.itemView, false); 8498 } 8499 } 8500 8501 @Override 8502 public void onAddFinished(ViewHolder item) { 8503 item.setIsRecyclable(true); 8504 if (!item.shouldBeKeptAsChild()) { 8505 removeAnimatingView(item.itemView); 8506 } 8507 } 8508 8509 @Override 8510 public void onMoveFinished(ViewHolder item) { 8511 item.setIsRecyclable(true); 8512 if (!item.shouldBeKeptAsChild()) { 8513 removeAnimatingView(item.itemView); 8514 } 8515 } 8516 8517 @Override 8518 public void onChangeFinished(ViewHolder item) { 8519 item.setIsRecyclable(true); 8520 /** 8521 * We check both shadowed and shadowing because a ViewHolder may get both roles at the 8522 * same time. 8523 * 8524 * Assume this flow: 8525 * item X is represented by VH_1. Then itemX changes, so we create VH_2 . 8526 * RV sets the following and calls item animator: 8527 * VH_1.shadowed = VH_2; 8528 * VH_1.mChanged = true; 8529 * VH_2.shadowing =VH_1; 8530 * 8531 * Then, before the first change finishes, item changes again so we create VH_3. 8532 * RV sets the following and calls item animator: 8533 * VH_2.shadowed = VH_3 8534 * VH_2.mChanged = true 8535 * VH_3.shadowing = VH_2 8536 * 8537 * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has 8538 * both shadowing and shadowed fields set. Shadowing information is obsolete now 8539 * because the first animation where VH_2 is newViewHolder is not valid anymore. 8540 * We ended up in this case because VH_2 played both roles. On the other hand, 8541 * we DO NOT want to clear its changed flag. 8542 * 8543 * If second change was simply reverting first change, we would find VH_1 in 8544 * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before 8545 * re-using 8546 */ 8547 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh 8548 item.mShadowedHolder = null; 8549 item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags); 8550 } 8551 // always null this because an OldViewHolder can never become NewViewHolder w/o being 8552 // recycled. 8553 item.mShadowingHolder = null; 8554 if (!item.shouldBeKeptAsChild()) { 8555 removeAnimatingView(item.itemView); 8556 } 8557 } 8558 }; 8559 8560 /** 8561 * This class defines the animations that take place on items as changes are made 8562 * to the adapter. 8563 * 8564 * Subclasses of ItemAnimator can be used to implement custom animations for actions on 8565 * ViewHolder items. The RecyclerView will manage retaining these items while they 8566 * are being animated, but implementors must call the appropriate "Starting" 8567 * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)}, 8568 * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or 8569 * {@link #dispatchAddStarting(ViewHolder)}) 8570 * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)}, 8571 * {@link #dispatchMoveFinished(ViewHolder)}, 8572 * {@link #dispatchChangeFinished(ViewHolder, boolean)}, 8573 * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is 8574 * being started and ended. 8575 * 8576 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}</p> 8577 * 8578 * @see #setItemAnimator(ItemAnimator) 8579 */ 8580 public static abstract class ItemAnimator { 8581 8582 private ItemAnimatorListener mListener = null; 8583 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = 8584 new ArrayList<ItemAnimatorFinishedListener>(); 8585 8586 private long mAddDuration = 120; 8587 private long mRemoveDuration = 120; 8588 private long mMoveDuration = 250; 8589 private long mChangeDuration = 250; 8590 8591 private boolean mSupportsChangeAnimations = true; 8592 8593 /** 8594 * Gets the current duration for which all move animations will run. 8595 * 8596 * @return The current move duration 8597 */ 8598 public long getMoveDuration() { 8599 return mMoveDuration; 8600 } 8601 8602 /** 8603 * Sets the duration for which all move animations will run. 8604 * 8605 * @param moveDuration The move duration 8606 */ 8607 public void setMoveDuration(long moveDuration) { 8608 mMoveDuration = moveDuration; 8609 } 8610 8611 /** 8612 * Gets the current duration for which all add animations will run. 8613 * 8614 * @return The current add duration 8615 */ 8616 public long getAddDuration() { 8617 return mAddDuration; 8618 } 8619 8620 /** 8621 * Sets the duration for which all add animations will run. 8622 * 8623 * @param addDuration The add duration 8624 */ 8625 public void setAddDuration(long addDuration) { 8626 mAddDuration = addDuration; 8627 } 8628 8629 /** 8630 * Gets the current duration for which all remove animations will run. 8631 * 8632 * @return The current remove duration 8633 */ 8634 public long getRemoveDuration() { 8635 return mRemoveDuration; 8636 } 8637 8638 /** 8639 * Sets the duration for which all remove animations will run. 8640 * 8641 * @param removeDuration The remove duration 8642 */ 8643 public void setRemoveDuration(long removeDuration) { 8644 mRemoveDuration = removeDuration; 8645 } 8646 8647 /** 8648 * Gets the current duration for which all change animations will run. 8649 * 8650 * @return The current change duration 8651 */ 8652 public long getChangeDuration() { 8653 return mChangeDuration; 8654 } 8655 8656 /** 8657 * Sets the duration for which all change animations will run. 8658 * 8659 * @param changeDuration The change duration 8660 */ 8661 public void setChangeDuration(long changeDuration) { 8662 mChangeDuration = changeDuration; 8663 } 8664 8665 /** 8666 * Returns whether this ItemAnimator supports animations of change events. 8667 * 8668 * @return true if change animations are supported, false otherwise 8669 */ 8670 public boolean getSupportsChangeAnimations() { 8671 return mSupportsChangeAnimations; 8672 } 8673 8674 /** 8675 * Sets whether this ItemAnimator supports animations of item change events. 8676 * If you set this property to false, actions on the data set which change the 8677 * contents of items will not be animated. What those animations are is left 8678 * up to the discretion of the ItemAnimator subclass, in its 8679 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation. 8680 * The value of this property is true by default. 8681 * 8682 * @see Adapter#notifyItemChanged(int) 8683 * @see Adapter#notifyItemRangeChanged(int, int) 8684 * 8685 * @param supportsChangeAnimations true if change animations are supported by 8686 * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator 8687 * will not receive a call to 8688 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur. 8689 */ 8690 public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { 8691 mSupportsChangeAnimations = supportsChangeAnimations; 8692 } 8693 8694 /** 8695 * Internal only: 8696 * Sets the listener that must be called when the animator is finished 8697 * animating the item (or immediately if no animation happens). This is set 8698 * internally and is not intended to be set by external code. 8699 * 8700 * @param listener The listener that must be called. 8701 */ 8702 void setListener(ItemAnimatorListener listener) { 8703 mListener = listener; 8704 } 8705 8706 /** 8707 * Called when there are pending animations waiting to be started. This state 8708 * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()}, 8709 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and 8710 * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the 8711 * RecyclerView that the ItemAnimator wants to be called later to start the 8712 * associated animations. runPendingAnimations() will be scheduled to be run 8713 * on the next frame. 8714 */ 8715 abstract public void runPendingAnimations(); 8716 8717 /** 8718 * Called when an item is removed from the RecyclerView. Implementors can choose 8719 * whether and how to animate that change, but must always call 8720 * {@link #dispatchRemoveFinished(ViewHolder)} when done, either 8721 * immediately (if no animation will occur) or after the animation actually finishes. 8722 * The return value indicates whether an animation has been set up and whether the 8723 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8724 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8725 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8726 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8727 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8728 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8729 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8730 * 8731 * <p>This method may also be called for disappearing items which continue to exist in the 8732 * RecyclerView, but for which the system does not have enough information to animate 8733 * them out of view. In that case, the default animation for removing items is run 8734 * on those items as well.</p> 8735 * 8736 * @param holder The item that is being removed. 8737 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8738 * false otherwise. 8739 */ 8740 abstract public boolean animateRemove(ViewHolder holder); 8741 8742 /** 8743 * Called when an item is added to the RecyclerView. Implementors can choose 8744 * whether and how to animate that change, but must always call 8745 * {@link #dispatchAddFinished(ViewHolder)} when done, either 8746 * immediately (if no animation will occur) or after the animation actually finishes. 8747 * The return value indicates whether an animation has been set up and whether the 8748 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8749 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8750 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8751 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8752 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8753 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8754 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8755 * 8756 * <p>This method may also be called for appearing items which were already in the 8757 * RecyclerView, but for which the system does not have enough information to animate 8758 * them into view. In that case, the default animation for adding items is run 8759 * on those items as well.</p> 8760 * 8761 * @param holder The item that is being added. 8762 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8763 * false otherwise. 8764 */ 8765 abstract public boolean animateAdd(ViewHolder holder); 8766 8767 /** 8768 * Called when an item is moved in the RecyclerView. Implementors can choose 8769 * whether and how to animate that change, but must always call 8770 * {@link #dispatchMoveFinished(ViewHolder)} when done, either 8771 * immediately (if no animation will occur) or after the animation actually finishes. 8772 * The return value indicates whether an animation has been set up and whether the 8773 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8774 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8775 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8776 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8777 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8778 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8779 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8780 * 8781 * @param holder The item that is being moved. 8782 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8783 * false otherwise. 8784 */ 8785 abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY, 8786 int toX, int toY); 8787 8788 /** 8789 * Called when an item is changed in the RecyclerView, as indicated by a call to 8790 * {@link Adapter#notifyItemChanged(int)} or 8791 * {@link Adapter#notifyItemRangeChanged(int, int)}. 8792 * <p> 8793 * Implementers can choose whether and how to animate changes, but must always call 8794 * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder, 8795 * either immediately (if no animation will occur) or after the animation actually finishes. 8796 * The return value indicates whether an animation has been set up and whether the 8797 * ItemAnimator's {@link #runPendingAnimations()} method should be called at the 8798 * next opportunity. This mechanism allows ItemAnimator to set up individual animations 8799 * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, 8800 * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, 8801 * {@link #animateRemove(ViewHolder) animateRemove()}, and 8802 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, 8803 * then start the animations together in the later call to {@link #runPendingAnimations()}. 8804 * 8805 * @param oldHolder The original item that changed. 8806 * @param newHolder The new item that was created with the changed content. Might be null 8807 * @param fromLeft Left of the old view holder 8808 * @param fromTop Top of the old view holder 8809 * @param toLeft Left of the new view holder 8810 * @param toTop Top of the new view holder 8811 * @return true if a later call to {@link #runPendingAnimations()} is requested, 8812 * false otherwise. 8813 */ 8814 abstract public boolean animateChange(ViewHolder oldHolder, 8815 ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); 8816 8817 8818 /** 8819 * Method to be called by subclasses when a remove animation is done. 8820 * 8821 * @param item The item which has been removed 8822 */ 8823 public final void dispatchRemoveFinished(ViewHolder item) { 8824 onRemoveFinished(item); 8825 if (mListener != null) { 8826 mListener.onRemoveFinished(item); 8827 } 8828 } 8829 8830 /** 8831 * Method to be called by subclasses when a move animation is done. 8832 * 8833 * @param item The item which has been moved 8834 */ 8835 public final void dispatchMoveFinished(ViewHolder item) { 8836 onMoveFinished(item); 8837 if (mListener != null) { 8838 mListener.onMoveFinished(item); 8839 } 8840 } 8841 8842 /** 8843 * Method to be called by subclasses when an add animation is done. 8844 * 8845 * @param item The item which has been added 8846 */ 8847 public final void dispatchAddFinished(ViewHolder item) { 8848 onAddFinished(item); 8849 if (mListener != null) { 8850 mListener.onAddFinished(item); 8851 } 8852 } 8853 8854 /** 8855 * Method to be called by subclasses when a change animation is done. 8856 * 8857 * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int) 8858 * @param item The item which has been changed (this method must be called for 8859 * each non-null ViewHolder passed into 8860 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). 8861 * @param oldItem true if this is the old item that was changed, false if 8862 * it is the new item that replaced the old item. 8863 */ 8864 public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { 8865 onChangeFinished(item, oldItem); 8866 if (mListener != null) { 8867 mListener.onChangeFinished(item); 8868 } 8869 } 8870 8871 /** 8872 * Method to be called by subclasses when a remove animation is being started. 8873 * 8874 * @param item The item being removed 8875 */ 8876 public final void dispatchRemoveStarting(ViewHolder item) { 8877 onRemoveStarting(item); 8878 } 8879 8880 /** 8881 * Method to be called by subclasses when a move animation is being started. 8882 * 8883 * @param item The item being moved 8884 */ 8885 public final void dispatchMoveStarting(ViewHolder item) { 8886 onMoveStarting(item); 8887 } 8888 8889 /** 8890 * Method to be called by subclasses when an add animation is being started. 8891 * 8892 * @param item The item being added 8893 */ 8894 public final void dispatchAddStarting(ViewHolder item) { 8895 onAddStarting(item); 8896 } 8897 8898 /** 8899 * Method to be called by subclasses when a change animation is being started. 8900 * 8901 * @param item The item which has been changed (this method must be called for 8902 * each non-null ViewHolder passed into 8903 * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). 8904 * @param oldItem true if this is the old item that was changed, false if 8905 * it is the new item that replaced the old item. 8906 */ 8907 public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) { 8908 onChangeStarting(item, oldItem); 8909 } 8910 8911 /** 8912 * Method called when an animation on a view should be ended immediately. 8913 * This could happen when other events, like scrolling, occur, so that 8914 * animating views can be quickly put into their proper end locations. 8915 * Implementations should ensure that any animations running on the item 8916 * are canceled and affected properties are set to their end values. 8917 * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} 8918 * should be called since the animations are effectively done when this 8919 * method is called. 8920 * 8921 * @param item The item for which an animation should be stopped. 8922 */ 8923 abstract public void endAnimation(ViewHolder item); 8924 8925 /** 8926 * Method called when all item animations should be ended immediately. 8927 * This could happen when other events, like scrolling, occur, so that 8928 * animating views can be quickly put into their proper end locations. 8929 * Implementations should ensure that any animations running on any items 8930 * are canceled and affected properties are set to their end values. 8931 * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} 8932 * should be called since the animations are effectively done when this 8933 * method is called. 8934 */ 8935 abstract public void endAnimations(); 8936 8937 /** 8938 * Method which returns whether there are any item animations currently running. 8939 * This method can be used to determine whether to delay other actions until 8940 * animations end. 8941 * 8942 * @return true if there are any item animations currently running, false otherwise. 8943 */ 8944 abstract public boolean isRunning(); 8945 8946 /** 8947 * Like {@link #isRunning()}, this method returns whether there are any item 8948 * animations currently running. Addtionally, the listener passed in will be called 8949 * when there are no item animations running, either immediately (before the method 8950 * returns) if no animations are currently running, or when the currently running 8951 * animations are {@link #dispatchAnimationsFinished() finished}. 8952 * 8953 * <p>Note that the listener is transient - it is either called immediately and not 8954 * stored at all, or stored only until it is called when running animations 8955 * are finished sometime later.</p> 8956 * 8957 * @param listener A listener to be called immediately if no animations are running 8958 * or later when currently-running animations have finished. A null listener is 8959 * equivalent to calling {@link #isRunning()}. 8960 * @return true if there are any item animations currently running, false otherwise. 8961 */ 8962 public final boolean isRunning(ItemAnimatorFinishedListener listener) { 8963 boolean running = isRunning(); 8964 if (listener != null) { 8965 if (!running) { 8966 listener.onAnimationsFinished(); 8967 } else { 8968 mFinishedListeners.add(listener); 8969 } 8970 } 8971 return running; 8972 } 8973 8974 /** 8975 * The interface to be implemented by listeners to animation events from this 8976 * ItemAnimator. This is used internally and is not intended for developers to 8977 * create directly. 8978 */ 8979 interface ItemAnimatorListener { 8980 void onRemoveFinished(ViewHolder item); 8981 void onAddFinished(ViewHolder item); 8982 void onMoveFinished(ViewHolder item); 8983 void onChangeFinished(ViewHolder item); 8984 } 8985 8986 /** 8987 * This method should be called by ItemAnimator implementations to notify 8988 * any listeners that all pending and active item animations are finished. 8989 */ 8990 public final void dispatchAnimationsFinished() { 8991 final int count = mFinishedListeners.size(); 8992 for (int i = 0; i < count; ++i) { 8993 mFinishedListeners.get(i).onAnimationsFinished(); 8994 } 8995 mFinishedListeners.clear(); 8996 } 8997 8998 /** 8999 * This interface is used to inform listeners when all pending or running animations 9000 * in an ItemAnimator are finished. This can be used, for example, to delay an action 9001 * in a data set until currently-running animations are complete. 9002 * 9003 * @see #isRunning(ItemAnimatorFinishedListener) 9004 */ 9005 public interface ItemAnimatorFinishedListener { 9006 void onAnimationsFinished(); 9007 } 9008 9009 /** 9010 * Called when a remove animation is being started on the given ViewHolder. 9011 * The default implementation does nothing. Subclasses may wish to override 9012 * this method to handle any ViewHolder-specific operations linked to animation 9013 * lifecycles. 9014 * 9015 * @param item The ViewHolder being animated. 9016 */ 9017 public void onRemoveStarting(ViewHolder item) {} 9018 9019 /** 9020 * Called when a remove animation has ended on the given ViewHolder. 9021 * The default implementation does nothing. Subclasses may wish to override 9022 * this method to handle any ViewHolder-specific operations linked to animation 9023 * lifecycles. 9024 * 9025 * @param item The ViewHolder being animated. 9026 */ 9027 public void onRemoveFinished(ViewHolder item) {} 9028 9029 /** 9030 * Called when an add animation is being started on the given ViewHolder. 9031 * The default implementation does nothing. Subclasses may wish to override 9032 * this method to handle any ViewHolder-specific operations linked to animation 9033 * lifecycles. 9034 * 9035 * @param item The ViewHolder being animated. 9036 */ 9037 public void onAddStarting(ViewHolder item) {} 9038 9039 /** 9040 * Called when an add animation has ended on the given ViewHolder. 9041 * The default implementation does nothing. Subclasses may wish to override 9042 * this method to handle any ViewHolder-specific operations linked to animation 9043 * lifecycles. 9044 * 9045 * @param item The ViewHolder being animated. 9046 */ 9047 public void onAddFinished(ViewHolder item) {} 9048 9049 /** 9050 * Called when a move animation is being started on the given ViewHolder. 9051 * The default implementation does nothing. Subclasses may wish to override 9052 * this method to handle any ViewHolder-specific operations linked to animation 9053 * lifecycles. 9054 * 9055 * @param item The ViewHolder being animated. 9056 */ 9057 public void onMoveStarting(ViewHolder item) {} 9058 9059 /** 9060 * Called when a move animation has ended on the given ViewHolder. 9061 * The default implementation does nothing. Subclasses may wish to override 9062 * this method to handle any ViewHolder-specific operations linked to animation 9063 * lifecycles. 9064 * 9065 * @param item The ViewHolder being animated. 9066 */ 9067 public void onMoveFinished(ViewHolder item) {} 9068 9069 /** 9070 * Called when a change animation is being started on the given ViewHolder. 9071 * The default implementation does nothing. Subclasses may wish to override 9072 * this method to handle any ViewHolder-specific operations linked to animation 9073 * lifecycles. 9074 * 9075 * @param item The ViewHolder being animated. 9076 * @param oldItem true if this is the old item that was changed, false if 9077 * it is the new item that replaced the old item. 9078 */ 9079 public void onChangeStarting(ViewHolder item, boolean oldItem) {} 9080 9081 /** 9082 * Called when a change animation has ended on the given ViewHolder. 9083 * The default implementation does nothing. Subclasses may wish to override 9084 * this method to handle any ViewHolder-specific operations linked to animation 9085 * lifecycles. 9086 * 9087 * @param item The ViewHolder being animated. 9088 * @param oldItem true if this is the old item that was changed, false if 9089 * it is the new item that replaced the old item. 9090 */ 9091 public void onChangeFinished(ViewHolder item, boolean oldItem) {} 9092 9093 } 9094 9095 /** 9096 * Internal data structure that holds information about an item's bounds. 9097 * This information is used in calculating item animations. 9098 */ 9099 private static class ItemHolderInfo { 9100 ViewHolder holder; 9101 int left, top, right, bottom; 9102 9103 ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) { 9104 this.holder = holder; 9105 this.left = left; 9106 this.top = top; 9107 this.right = right; 9108 this.bottom = bottom; 9109 } 9110 } 9111} 9112