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