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