RecyclerView.java revision b31c3281d870e9abb673db239234d580dcc4feff
1/* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package androidx.recyclerview.widget; 19 20import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21import static androidx.core.view.ViewCompat.TYPE_NON_TOUCH; 22import static androidx.core.view.ViewCompat.TYPE_TOUCH; 23 24import android.content.Context; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.database.Observable; 28import android.graphics.Canvas; 29import android.graphics.Matrix; 30import android.graphics.PointF; 31import android.graphics.Rect; 32import android.graphics.RectF; 33import android.graphics.drawable.Drawable; 34import android.graphics.drawable.StateListDrawable; 35import android.os.Build; 36import android.os.Bundle; 37import android.os.Parcel; 38import android.os.Parcelable; 39import android.os.SystemClock; 40import androidx.annotation.CallSuper; 41import androidx.annotation.IntDef; 42import androidx.annotation.NonNull; 43import androidx.annotation.Nullable; 44import androidx.annotation.Px; 45import androidx.annotation.RestrictTo; 46import androidx.annotation.VisibleForTesting; 47import androidx.core.os.TraceCompat; 48import androidx.core.util.Preconditions; 49import androidx.customview.view.AbsSavedState; 50import androidx.core.view.InputDeviceCompat; 51import androidx.core.view.MotionEventCompat; 52import androidx.core.view.NestedScrollingChild2; 53import androidx.core.view.NestedScrollingChildHelper; 54import androidx.core.view.ScrollingView; 55import androidx.core.view.ViewCompat; 56import androidx.core.view.ViewConfigurationCompat; 57import androidx.core.view.accessibility.AccessibilityEventCompat; 58import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 59import androidx.core.widget.EdgeEffectCompat; 60import androidx.recyclerview.R; 61import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo; 62import androidx.viewpager.widget.ViewPager; 63 64import android.util.AttributeSet; 65import android.util.Log; 66import android.util.SparseArray; 67import android.view.Display; 68import android.view.FocusFinder; 69import android.view.InputDevice; 70import android.view.MotionEvent; 71import android.view.VelocityTracker; 72import android.view.View; 73import android.view.ViewConfiguration; 74import android.view.ViewGroup; 75import android.view.ViewParent; 76import android.view.accessibility.AccessibilityEvent; 77import android.view.accessibility.AccessibilityManager; 78import android.view.animation.Interpolator; 79import android.widget.EdgeEffect; 80import android.widget.LinearLayout; 81import android.widget.OverScroller; 82 83import java.lang.annotation.Retention; 84import java.lang.annotation.RetentionPolicy; 85import java.lang.ref.WeakReference; 86import java.lang.reflect.Constructor; 87import java.lang.reflect.InvocationTargetException; 88import java.util.ArrayList; 89import java.util.Collections; 90import java.util.List; 91 92/** 93 * A flexible view for providing a limited window into a large data set. 94 * 95 * <h3>Glossary of terms:</h3> 96 * 97 * <ul> 98 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 99 * that represent items in a data set.</li> 100 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 101 * <li><em>Index:</em> The index of an attached child view as used in a call to 102 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 103 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 104 * to a <em>position</em> within the adapter.</li> 105 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 106 * position may be placed in a cache for later reuse to display the same type of data again 107 * later. This can drastically improve performance by skipping initial layout inflation 108 * or construction.</li> 109 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 110 * state during layout. Scrap views may be reused without becoming fully detached 111 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 112 * by the adapter if the view was considered <em>dirty</em>.</li> 113 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 114 * being displayed.</li> 115 * </ul> 116 * 117 * <h4>Positions in RecyclerView:</h4> 118 * <p> 119 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and 120 * {@link LayoutManager} to be able to detect data set changes in batches during a layout 121 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. 122 * It also helps with performance because all view bindings happen at the same time and unnecessary 123 * bindings are avoided. 124 * <p> 125 * For this reason, there are two types of <code>position</code> related methods in RecyclerView: 126 * <ul> 127 * <li>layout position: Position of an item in the latest layout calculation. This is the 128 * position from the LayoutManager's perspective.</li> 129 * <li>adapter position: Position of an item in the adapter. This is the position from 130 * the Adapter's perspective.</li> 131 * </ul> 132 * <p> 133 * These two positions are the same except the time between dispatching <code>adapter.notify* 134 * </code> events and calculating the updated layout. 135 * <p> 136 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest 137 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, 138 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the 139 * last layout calculation. You can rely on these positions to be consistent with what user is 140 * currently seeing on the screen. For example, if you have a list of items on the screen and user 141 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user 142 * is seeing. 143 * <p> 144 * The other set of position related methods are in the form of 145 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()}, 146 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to 147 * work with up-to-date adapter positions even if they may not have been reflected to layout yet. 148 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use 149 * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate 150 * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has 151 * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or 152 * <code>null</code> results from these methods. 153 * <p> 154 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when 155 * writing an {@link Adapter}, you probably want to use adapter positions. 156 * 157 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_layoutManager 158 */ 159public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 { 160 161 static final String TAG = "RecyclerView"; 162 163 static final boolean DEBUG = false; 164 165 static final boolean VERBOSE_TRACING = false; 166 167 private static final int[] NESTED_SCROLLING_ATTRS = 168 {16843830 /* android.R.attr.nestedScrollingEnabled */}; 169 170 private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding}; 171 172 /** 173 * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if 174 * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by 175 * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler 176 * recursively traverses itemView and invalidates display list for each ViewGroup that matches 177 * this criteria. 178 */ 179 static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 180 || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; 181 /** 182 * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, 183 * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to 184 * 0 when mode is unspecified. 185 */ 186 static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; 187 188 static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16; 189 190 /** 191 * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to 192 * RenderThread but before the next frame begins. We schedule prefetch work in this window. 193 */ 194 private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; 195 196 /** 197 * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction. 198 * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT. 199 */ 200 private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15; 201 202 /** 203 * on API 15-, a focused child can still be considered a focused child of RV even after 204 * it's being removed or its focusable flag is set to false. This is because when this focused 205 * child is detached, the reference to this child is not removed in clearFocus. API 16 and above 206 * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus 207 * to request focus on a new child, which will clear the focus on the old (detached) child as a 208 * side-effect. 209 */ 210 private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15; 211 212 static final boolean DISPATCH_TEMP_DETACH = false; 213 214 /** @hide */ 215 @RestrictTo(LIBRARY_GROUP) 216 @IntDef({HORIZONTAL, VERTICAL}) 217 @Retention(RetentionPolicy.SOURCE) 218 public @interface Orientation {} 219 220 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 221 public static final int VERTICAL = LinearLayout.VERTICAL; 222 223 static final int DEFAULT_ORIENTATION = VERTICAL; 224 public static final int NO_POSITION = -1; 225 public static final long NO_ID = -1; 226 public static final int INVALID_TYPE = -1; 227 228 /** 229 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 230 * that the RecyclerView should use the standard touch slop for smooth, 231 * continuous scrolling. 232 */ 233 public static final int TOUCH_SLOP_DEFAULT = 0; 234 235 /** 236 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 237 * that the RecyclerView should use the standard touch slop for scrolling 238 * widgets that snap to a page or other coarse-grained barrier. 239 */ 240 public static final int TOUCH_SLOP_PAGING = 1; 241 242 static final int MAX_SCROLL_DURATION = 2000; 243 244 /** 245 * RecyclerView is calculating a scroll. 246 * If there are too many of these in Systrace, some Views inside RecyclerView might be causing 247 * it. Try to avoid using EditText, focusable views or handle them with care. 248 */ 249 static final String TRACE_SCROLL_TAG = "RV Scroll"; 250 251 /** 252 * OnLayout has been called by the View system. 253 * If this shows up too many times in Systrace, make sure the children of RecyclerView do not 254 * update themselves directly. This will cause a full re-layout but when it happens via the 255 * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation. 256 */ 257 private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout"; 258 259 /** 260 * NotifyDataSetChanged or equal has been called. 261 * If this is taking a long time, try sending granular notify adapter changes instead of just 262 * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter 263 * might help. 264 */ 265 private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate"; 266 267 /** 268 * RecyclerView is doing a layout for partial adapter updates (we know what has changed) 269 * If this is taking a long time, you may have dispatched too many Adapter updates causing too 270 * many Views being rebind. Make sure all are necessary and also prefer using notify*Range 271 * methods. 272 */ 273 private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate"; 274 275 /** 276 * RecyclerView is rebinding a View. 277 * If this is taking a lot of time, consider optimizing your layout or make sure you are not 278 * doing extra operations in onBindViewHolder call. 279 */ 280 static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; 281 282 /** 283 * RecyclerView is attempting to pre-populate off screen views. 284 */ 285 static final String TRACE_PREFETCH_TAG = "RV Prefetch"; 286 287 /** 288 * RecyclerView is attempting to pre-populate off screen itemviews within an off screen 289 * RecyclerView. 290 */ 291 static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch"; 292 293 /** 294 * RecyclerView is creating a new View. 295 * If too many of these present in Systrace: 296 * - There might be a problem in Recycling (e.g. custom Animations that set transient state and 297 * prevent recycling or ItemAnimator not implementing the contract properly. ({@link 298 * > Adapter#onFailedToRecycleView(ViewHolder)}) 299 * 300 * - There might be too many item view types. 301 * > Try merging them 302 * 303 * - There might be too many itemChange animations and not enough space in RecyclerPool. 304 * >Try increasing your pool size and item cache size. 305 */ 306 static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; 307 private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = 308 new Class[]{Context.class, AttributeSet.class, int.class, int.class}; 309 310 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 311 312 final Recycler mRecycler = new Recycler(); 313 314 private SavedState mPendingSavedState; 315 316 /** 317 * Handles adapter updates 318 */ 319 AdapterHelper mAdapterHelper; 320 321 /** 322 * Handles abstraction between LayoutManager children and RecyclerView children 323 */ 324 ChildHelper mChildHelper; 325 326 /** 327 * Keeps data about views to be used for animations 328 */ 329 final ViewInfoStore mViewInfoStore = new ViewInfoStore(); 330 331 /** 332 * Prior to L, there is no way to query this variable which is why we override the setter and 333 * track it here. 334 */ 335 boolean mClipToPadding; 336 337 /** 338 * Note: this Runnable is only ever posted if: 339 * 1) We've been through first layout 340 * 2) We know we have a fixed size (mHasFixedSize) 341 * 3) We're attached 342 */ 343 final Runnable mUpdateChildViewsRunnable = new Runnable() { 344 @Override 345 public void run() { 346 if (!mFirstLayoutComplete || isLayoutRequested()) { 347 // a layout request will happen, we should not do layout here. 348 return; 349 } 350 if (!mIsAttached) { 351 requestLayout(); 352 // if we are not attached yet, mark us as requiring layout and skip 353 return; 354 } 355 if (mLayoutFrozen) { 356 mLayoutWasDefered = true; 357 return; //we'll process updates when ice age ends. 358 } 359 consumePendingUpdateOperations(); 360 } 361 }; 362 363 final Rect mTempRect = new Rect(); 364 private final Rect mTempRect2 = new Rect(); 365 final RectF mTempRectF = new RectF(); 366 Adapter mAdapter; 367 @VisibleForTesting LayoutManager mLayout; 368 RecyclerListener mRecyclerListener; 369 final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>(); 370 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 371 new ArrayList<>(); 372 private OnItemTouchListener mActiveOnItemTouchListener; 373 boolean mIsAttached; 374 boolean mHasFixedSize; 375 boolean mEnableFastScroller; 376 @VisibleForTesting boolean mFirstLayoutComplete; 377 378 /** 379 * The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of 380 * calls to {@link #startInterceptRequestLayout()} - number of calls to 381 * {@link #stopInterceptRequestLayout(boolean)} . This is used to signal whether we 382 * should defer layout operations caused by layout requests from children of 383 * {@link RecyclerView}. 384 */ 385 private int mInterceptRequestLayoutDepth = 0; 386 387 /** 388 * True if a call to requestLayout was intercepted and prevented from executing like normal and 389 * we plan on continuing with normal execution later. 390 */ 391 boolean mLayoutWasDefered; 392 393 boolean mLayoutFrozen; 394 private boolean mIgnoreMotionEventTillDown; 395 396 // binary OR of change events that were eaten during a layout or scroll. 397 private int mEatenAccessibilityChangeFlags; 398 boolean mAdapterUpdateDuringMeasure; 399 400 private final AccessibilityManager mAccessibilityManager; 401 private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners; 402 403 /** 404 * True after an event occurs that signals that the entire data set has changed. In that case, 405 * we cannot run any animations since we don't know what happened until layout. 406 * 407 * Attached items are invalid until next layout, at which point layout will animate/replace 408 * items as necessary, building up content from the (effectively) new adapter from scratch. 409 * 410 * Cached items must be discarded when setting this to true, so that the cache may be freely 411 * used by prefetching until the next layout occurs. 412 * 413 * @see #processDataSetCompletelyChanged(boolean) 414 */ 415 boolean mDataSetHasChangedAfterLayout = false; 416 417 /** 418 * True after the data set has completely changed and 419 * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent 420 * measure/layout. 421 * 422 * @see #processDataSetCompletelyChanged(boolean) 423 */ 424 boolean mDispatchItemsChangedEvent = false; 425 426 /** 427 * This variable is incremented during a dispatchLayout and/or scroll. 428 * Some methods should not be called during these periods (e.g. adapter data change). 429 * Doing so will create hard to find bugs so we better check it and throw an exception. 430 * 431 * @see #assertInLayoutOrScroll(String) 432 * @see #assertNotInLayoutOrScroll(String) 433 */ 434 private int mLayoutOrScrollCounter = 0; 435 436 /** 437 * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception 438 * (for API compatibility). 439 * <p> 440 * It is a bad practice for a developer to update the data in a scroll callback since it is 441 * potentially called during a layout. 442 */ 443 private int mDispatchScrollCounter = 0; 444 445 @NonNull 446 private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory(); 447 private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 448 449 ItemAnimator mItemAnimator = new DefaultItemAnimator(); 450 451 private static final int INVALID_POINTER = -1; 452 453 /** 454 * The RecyclerView is not currently scrolling. 455 * @see #getScrollState() 456 */ 457 public static final int SCROLL_STATE_IDLE = 0; 458 459 /** 460 * The RecyclerView is currently being dragged by outside input such as user touch input. 461 * @see #getScrollState() 462 */ 463 public static final int SCROLL_STATE_DRAGGING = 1; 464 465 /** 466 * The RecyclerView is currently animating to a final position while not under 467 * outside control. 468 * @see #getScrollState() 469 */ 470 public static final int SCROLL_STATE_SETTLING = 2; 471 472 static final long FOREVER_NS = Long.MAX_VALUE; 473 474 // Touch/scrolling handling 475 476 private int mScrollState = SCROLL_STATE_IDLE; 477 private int mScrollPointerId = INVALID_POINTER; 478 private VelocityTracker mVelocityTracker; 479 private int mInitialTouchX; 480 private int mInitialTouchY; 481 private int mLastTouchX; 482 private int mLastTouchY; 483 private int mTouchSlop; 484 private OnFlingListener mOnFlingListener; 485 private final int mMinFlingVelocity; 486 private final int mMaxFlingVelocity; 487 488 // This value is used when handling rotary encoder generic motion events. 489 private float mScaledHorizontalScrollFactor = Float.MIN_VALUE; 490 private float mScaledVerticalScrollFactor = Float.MIN_VALUE; 491 492 private boolean mPreserveFocusAfterLayout = true; 493 494 final ViewFlinger mViewFlinger = new ViewFlinger(); 495 496 GapWorker mGapWorker; 497 GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = 498 ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; 499 500 final State mState = new State(); 501 502 private OnScrollListener mScrollListener; 503 private List<OnScrollListener> mScrollListeners; 504 505 // For use in item animations 506 boolean mItemsAddedOrRemoved = false; 507 boolean mItemsChanged = false; 508 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = 509 new ItemAnimatorRestoreListener(); 510 boolean mPostedAnimatorRunner = false; 511 RecyclerViewAccessibilityDelegate mAccessibilityDelegate; 512 private ChildDrawingOrderCallback mChildDrawingOrderCallback; 513 514 // simple array to keep min and max child position during a layout calculation 515 // preserved not to create a new one in each layout pass 516 private final int[] mMinMaxLayoutPositions = new int[2]; 517 518 private NestedScrollingChildHelper mScrollingChildHelper; 519 private final int[] mScrollOffset = new int[2]; 520 private final int[] mScrollConsumed = new int[2]; 521 private final int[] mNestedOffsets = new int[2]; 522 523 /** 524 * Reusable int array for use in calls to {@link #scrollStep(int, int, int[])} so that the 525 * method may mutate it to "return" 2 ints. 526 */ 527 private final int[] mScrollStepConsumed = new int[2]; 528 529 /** 530 * These are views that had their a11y importance changed during a layout. We defer these events 531 * until the end of the layout because a11y service may make sync calls back to the RV while 532 * the View's state is undefined. 533 */ 534 @VisibleForTesting 535 final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList<>(); 536 537 private Runnable mItemAnimatorRunner = new Runnable() { 538 @Override 539 public void run() { 540 if (mItemAnimator != null) { 541 mItemAnimator.runPendingAnimations(); 542 } 543 mPostedAnimatorRunner = false; 544 } 545 }; 546 547 static final Interpolator sQuinticInterpolator = new Interpolator() { 548 @Override 549 public float getInterpolation(float t) { 550 t -= 1.0f; 551 return t * t * t * t * t + 1.0f; 552 } 553 }; 554 555 /** 556 * The callback to convert view info diffs into animations. 557 */ 558 private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = 559 new ViewInfoStore.ProcessCallback() { 560 @Override 561 public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, 562 @Nullable ItemHolderInfo postInfo) { 563 mRecycler.unscrapView(viewHolder); 564 animateDisappearance(viewHolder, info, postInfo); 565 } 566 @Override 567 public void processAppeared(ViewHolder viewHolder, 568 ItemHolderInfo preInfo, ItemHolderInfo info) { 569 animateAppearance(viewHolder, preInfo, info); 570 } 571 572 @Override 573 public void processPersistent(ViewHolder viewHolder, 574 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 575 viewHolder.setIsRecyclable(false); 576 if (mDataSetHasChangedAfterLayout) { 577 // since it was rebound, use change instead as we'll be mapping them from 578 // stable ids. If stable ids were false, we would not be running any 579 // animations 580 if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, 581 postInfo)) { 582 postAnimationRunner(); 583 } 584 } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { 585 postAnimationRunner(); 586 } 587 } 588 @Override 589 public void unused(ViewHolder viewHolder) { 590 mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); 591 } 592 }; 593 594 public RecyclerView(@NonNull Context context) { 595 this(context, null); 596 } 597 598 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 599 this(context, attrs, 0); 600 } 601 602 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 603 super(context, attrs, defStyle); 604 if (attrs != null) { 605 TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0); 606 mClipToPadding = a.getBoolean(0, true); 607 a.recycle(); 608 } else { 609 mClipToPadding = true; 610 } 611 setScrollContainer(true); 612 setFocusableInTouchMode(true); 613 614 final ViewConfiguration vc = ViewConfiguration.get(context); 615 mTouchSlop = vc.getScaledTouchSlop(); 616 mScaledHorizontalScrollFactor = 617 ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); 618 mScaledVerticalScrollFactor = 619 ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); 620 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 621 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 622 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 623 624 mItemAnimator.setListener(mItemAnimatorListener); 625 initAdapterManager(); 626 initChildrenHelper(); 627 // If not explicitly specified this view is important for accessibility. 628 if (ViewCompat.getImportantForAccessibility(this) 629 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 630 ViewCompat.setImportantForAccessibility(this, 631 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 632 } 633 mAccessibilityManager = (AccessibilityManager) getContext() 634 .getSystemService(Context.ACCESSIBILITY_SERVICE); 635 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 636 // Create the layoutManager if specified. 637 638 boolean nestedScrollingEnabled = true; 639 640 if (attrs != null) { 641 int defStyleRes = 0; 642 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 643 defStyle, defStyleRes); 644 String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); 645 int descendantFocusability = a.getInt( 646 R.styleable.RecyclerView_android_descendantFocusability, -1); 647 if (descendantFocusability == -1) { 648 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 649 } 650 mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); 651 if (mEnableFastScroller) { 652 StateListDrawable verticalThumbDrawable = (StateListDrawable) a 653 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); 654 Drawable verticalTrackDrawable = a 655 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); 656 StateListDrawable horizontalThumbDrawable = (StateListDrawable) a 657 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); 658 Drawable horizontalTrackDrawable = a 659 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); 660 initFastScroller(verticalThumbDrawable, verticalTrackDrawable, 661 horizontalThumbDrawable, horizontalTrackDrawable); 662 } 663 a.recycle(); 664 createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); 665 666 if (Build.VERSION.SDK_INT >= 21) { 667 a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, 668 defStyle, defStyleRes); 669 nestedScrollingEnabled = a.getBoolean(0, true); 670 a.recycle(); 671 } 672 } else { 673 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 674 } 675 676 // Re-set whether nested scrolling is enabled so that it is set on all API levels 677 setNestedScrollingEnabled(nestedScrollingEnabled); 678 } 679 680 /** 681 * Label appended to all public exception strings, used to help find which RV in an app is 682 * hitting an exception. 683 */ 684 String exceptionLabel() { 685 return " " + super.toString() 686 + ", adapter:" + mAdapter 687 + ", layout:" + mLayout 688 + ", context:" + getContext(); 689 } 690 691 /** 692 * Returns the accessibility delegate compatibility implementation used by the RecyclerView. 693 * @return An instance of AccessibilityDelegateCompat used by RecyclerView 694 */ 695 @Nullable 696 public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { 697 return mAccessibilityDelegate; 698 } 699 700 /** 701 * Sets the accessibility delegate compatibility implementation used by RecyclerView. 702 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. 703 */ 704 public void setAccessibilityDelegateCompat( 705 @Nullable RecyclerViewAccessibilityDelegate accessibilityDelegate) { 706 mAccessibilityDelegate = accessibilityDelegate; 707 ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); 708 } 709 710 /** 711 * Instantiate and set a LayoutManager, if specified in the attributes. 712 */ 713 private void createLayoutManager(Context context, String className, AttributeSet attrs, 714 int defStyleAttr, int defStyleRes) { 715 if (className != null) { 716 className = className.trim(); 717 if (!className.isEmpty()) { 718 className = getFullClassName(context, className); 719 try { 720 ClassLoader classLoader; 721 if (isInEditMode()) { 722 // Stupid layoutlib cannot handle simple class loaders. 723 classLoader = this.getClass().getClassLoader(); 724 } else { 725 classLoader = context.getClassLoader(); 726 } 727 Class<? extends LayoutManager> layoutManagerClass = 728 classLoader.loadClass(className).asSubclass(LayoutManager.class); 729 Constructor<? extends LayoutManager> constructor; 730 Object[] constructorArgs = null; 731 try { 732 constructor = layoutManagerClass 733 .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE); 734 constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes}; 735 } catch (NoSuchMethodException e) { 736 try { 737 constructor = layoutManagerClass.getConstructor(); 738 } catch (NoSuchMethodException e1) { 739 e1.initCause(e); 740 throw new IllegalStateException(attrs.getPositionDescription() 741 + ": Error creating LayoutManager " + className, e1); 742 } 743 } 744 constructor.setAccessible(true); 745 setLayoutManager(constructor.newInstance(constructorArgs)); 746 } catch (ClassNotFoundException e) { 747 throw new IllegalStateException(attrs.getPositionDescription() 748 + ": Unable to find LayoutManager " + className, e); 749 } catch (InvocationTargetException e) { 750 throw new IllegalStateException(attrs.getPositionDescription() 751 + ": Could not instantiate the LayoutManager: " + className, e); 752 } catch (InstantiationException e) { 753 throw new IllegalStateException(attrs.getPositionDescription() 754 + ": Could not instantiate the LayoutManager: " + className, e); 755 } catch (IllegalAccessException e) { 756 throw new IllegalStateException(attrs.getPositionDescription() 757 + ": Cannot access non-public constructor " + className, e); 758 } catch (ClassCastException e) { 759 throw new IllegalStateException(attrs.getPositionDescription() 760 + ": Class is not a LayoutManager " + className, e); 761 } 762 } 763 } 764 } 765 766 private String getFullClassName(Context context, String className) { 767 if (className.charAt(0) == '.') { 768 return context.getPackageName() + className; 769 } 770 if (className.contains(".")) { 771 return className; 772 } 773 return RecyclerView.class.getPackage().getName() + '.' + className; 774 } 775 776 private void initChildrenHelper() { 777 mChildHelper = new ChildHelper(new ChildHelper.Callback() { 778 @Override 779 public int getChildCount() { 780 return RecyclerView.this.getChildCount(); 781 } 782 783 @Override 784 public void addView(View child, int index) { 785 if (VERBOSE_TRACING) { 786 TraceCompat.beginSection("RV addView"); 787 } 788 RecyclerView.this.addView(child, index); 789 if (VERBOSE_TRACING) { 790 TraceCompat.endSection(); 791 } 792 dispatchChildAttached(child); 793 } 794 795 @Override 796 public int indexOfChild(View view) { 797 return RecyclerView.this.indexOfChild(view); 798 } 799 800 @Override 801 public void removeViewAt(int index) { 802 final View child = RecyclerView.this.getChildAt(index); 803 if (child != null) { 804 dispatchChildDetached(child); 805 806 // Clear any android.view.animation.Animation that may prevent the item from 807 // detaching when being removed. If a child is re-added before the 808 // lazy detach occurs, it will receive invalid attach/detach sequencing. 809 child.clearAnimation(); 810 } 811 if (VERBOSE_TRACING) { 812 TraceCompat.beginSection("RV removeViewAt"); 813 } 814 RecyclerView.this.removeViewAt(index); 815 if (VERBOSE_TRACING) { 816 TraceCompat.endSection(); 817 } 818 } 819 820 @Override 821 public View getChildAt(int offset) { 822 return RecyclerView.this.getChildAt(offset); 823 } 824 825 @Override 826 public void removeAllViews() { 827 final int count = getChildCount(); 828 for (int i = 0; i < count; i++) { 829 View child = getChildAt(i); 830 dispatchChildDetached(child); 831 832 // Clear any android.view.animation.Animation that may prevent the item from 833 // detaching when being removed. If a child is re-added before the 834 // lazy detach occurs, it will receive invalid attach/detach sequencing. 835 child.clearAnimation(); 836 } 837 RecyclerView.this.removeAllViews(); 838 } 839 840 @Override 841 public ViewHolder getChildViewHolder(View view) { 842 return getChildViewHolderInt(view); 843 } 844 845 @Override 846 public void attachViewToParent(View child, int index, 847 ViewGroup.LayoutParams layoutParams) { 848 final ViewHolder vh = getChildViewHolderInt(child); 849 if (vh != null) { 850 if (!vh.isTmpDetached() && !vh.shouldIgnore()) { 851 throw new IllegalArgumentException("Called attach on a child which is not" 852 + " detached: " + vh + exceptionLabel()); 853 } 854 if (DEBUG) { 855 Log.d(TAG, "reAttach " + vh); 856 } 857 vh.clearTmpDetachFlag(); 858 } 859 RecyclerView.this.attachViewToParent(child, index, layoutParams); 860 } 861 862 @Override 863 public void detachViewFromParent(int offset) { 864 final View view = getChildAt(offset); 865 if (view != null) { 866 final ViewHolder vh = getChildViewHolderInt(view); 867 if (vh != null) { 868 if (vh.isTmpDetached() && !vh.shouldIgnore()) { 869 throw new IllegalArgumentException("called detach on an already" 870 + " detached child " + vh + exceptionLabel()); 871 } 872 if (DEBUG) { 873 Log.d(TAG, "tmpDetach " + vh); 874 } 875 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); 876 } 877 } 878 RecyclerView.this.detachViewFromParent(offset); 879 } 880 881 @Override 882 public void onEnteredHiddenState(View child) { 883 final ViewHolder vh = getChildViewHolderInt(child); 884 if (vh != null) { 885 vh.onEnteredHiddenState(RecyclerView.this); 886 } 887 } 888 889 @Override 890 public void onLeftHiddenState(View child) { 891 final ViewHolder vh = getChildViewHolderInt(child); 892 if (vh != null) { 893 vh.onLeftHiddenState(RecyclerView.this); 894 } 895 } 896 }); 897 } 898 899 void initAdapterManager() { 900 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { 901 @Override 902 public ViewHolder findViewHolder(int position) { 903 final ViewHolder vh = findViewHolderForPosition(position, true); 904 if (vh == null) { 905 return null; 906 } 907 // ensure it is not hidden because for adapter helper, the only thing matter is that 908 // LM thinks view is a child. 909 if (mChildHelper.isHidden(vh.itemView)) { 910 if (DEBUG) { 911 Log.d(TAG, "assuming view holder cannot be find because it is hidden"); 912 } 913 return null; 914 } 915 return vh; 916 } 917 918 @Override 919 public void offsetPositionsForRemovingInvisible(int start, int count) { 920 offsetPositionRecordsForRemove(start, count, true); 921 mItemsAddedOrRemoved = true; 922 mState.mDeletedInvisibleItemCountSincePreviousLayout += count; 923 } 924 925 @Override 926 public void offsetPositionsForRemovingLaidOutOrNewView( 927 int positionStart, int itemCount) { 928 offsetPositionRecordsForRemove(positionStart, itemCount, false); 929 mItemsAddedOrRemoved = true; 930 } 931 932 933 @Override 934 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { 935 viewRangeUpdate(positionStart, itemCount, payload); 936 mItemsChanged = true; 937 } 938 939 @Override 940 public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { 941 dispatchUpdate(op); 942 } 943 944 void dispatchUpdate(AdapterHelper.UpdateOp op) { 945 switch (op.cmd) { 946 case AdapterHelper.UpdateOp.ADD: 947 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); 948 break; 949 case AdapterHelper.UpdateOp.REMOVE: 950 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); 951 break; 952 case AdapterHelper.UpdateOp.UPDATE: 953 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, 954 op.payload); 955 break; 956 case AdapterHelper.UpdateOp.MOVE: 957 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); 958 break; 959 } 960 } 961 962 @Override 963 public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { 964 dispatchUpdate(op); 965 } 966 967 @Override 968 public void offsetPositionsForAdd(int positionStart, int itemCount) { 969 offsetPositionRecordsForInsert(positionStart, itemCount); 970 mItemsAddedOrRemoved = true; 971 } 972 973 @Override 974 public void offsetPositionsForMove(int from, int to) { 975 offsetPositionRecordsForMove(from, to); 976 // should we create mItemsMoved ? 977 mItemsAddedOrRemoved = true; 978 } 979 }); 980 } 981 982 /** 983 * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's 984 * size is not affected by the adapter contents. RecyclerView can still change its size based 985 * on other factors (e.g. its parent's size) but this size calculation cannot depend on the 986 * size of its children or contents of its adapter (except the number of items in the adapter). 987 * <p> 988 * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow 989 * RecyclerView to avoid invalidating the whole layout when its adapter contents change. 990 * 991 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 992 */ 993 public void setHasFixedSize(boolean hasFixedSize) { 994 mHasFixedSize = hasFixedSize; 995 } 996 997 /** 998 * @return true if the app has specified that changes in adapter content cannot change 999 * the size of the RecyclerView itself. 1000 */ 1001 public boolean hasFixedSize() { 1002 return mHasFixedSize; 1003 } 1004 1005 @Override 1006 public void setClipToPadding(boolean clipToPadding) { 1007 if (clipToPadding != mClipToPadding) { 1008 invalidateGlows(); 1009 } 1010 mClipToPadding = clipToPadding; 1011 super.setClipToPadding(clipToPadding); 1012 if (mFirstLayoutComplete) { 1013 requestLayout(); 1014 } 1015 } 1016 1017 /** 1018 * Returns whether this RecyclerView will clip its children to its padding, and resize (but 1019 * not clip) any EdgeEffect to the padded region, if padding is present. 1020 * <p> 1021 * By default, children are clipped to the padding of their parent 1022 * RecyclerView. This clipping behavior is only enabled if padding is non-zero. 1023 * 1024 * @return true if this RecyclerView clips children to its padding and resizes (but doesn't 1025 * clip) any EdgeEffect to the padded region, false otherwise. 1026 * 1027 * @attr name android:clipToPadding 1028 */ 1029 @Override 1030 public boolean getClipToPadding() { 1031 return mClipToPadding; 1032 } 1033 1034 /** 1035 * Configure the scrolling touch slop for a specific use case. 1036 * 1037 * Set up the RecyclerView's scrolling motion threshold based on common usages. 1038 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. 1039 * 1040 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing 1041 * the intended usage of this RecyclerView 1042 */ 1043 public void setScrollingTouchSlop(int slopConstant) { 1044 final ViewConfiguration vc = ViewConfiguration.get(getContext()); 1045 switch (slopConstant) { 1046 default: 1047 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " 1048 + slopConstant + "; using default value"); 1049 // fall-through 1050 case TOUCH_SLOP_DEFAULT: 1051 mTouchSlop = vc.getScaledTouchSlop(); 1052 break; 1053 1054 case TOUCH_SLOP_PAGING: 1055 mTouchSlop = vc.getScaledPagingTouchSlop(); 1056 break; 1057 } 1058 } 1059 1060 /** 1061 * Swaps the current adapter with the provided one. It is similar to 1062 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same 1063 * {@link ViewHolder} and does not clear the RecycledViewPool. 1064 * <p> 1065 * Note that it still calls onAdapterChanged callbacks. 1066 * 1067 * @param adapter The new adapter to set, or null to set no adapter. 1068 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing 1069 * Views. If adapters have stable ids and/or you want to 1070 * animate the disappearing views, you may prefer to set 1071 * this to false. 1072 * @see #setAdapter(Adapter) 1073 */ 1074 public void swapAdapter(@Nullable Adapter adapter, boolean removeAndRecycleExistingViews) { 1075 // bail out if layout is frozen 1076 setLayoutFrozen(false); 1077 setAdapterInternal(adapter, true, removeAndRecycleExistingViews); 1078 processDataSetCompletelyChanged(true); 1079 requestLayout(); 1080 } 1081 /** 1082 * Set a new adapter to provide child views on demand. 1083 * <p> 1084 * When adapter is changed, all existing views are recycled back to the pool. If the pool has 1085 * only one adapter, it will be cleared. 1086 * 1087 * @param adapter The new adapter to set, or null to set no adapter. 1088 * @see #swapAdapter(Adapter, boolean) 1089 */ 1090 public void setAdapter(@Nullable Adapter adapter) { 1091 // bail out if layout is frozen 1092 setLayoutFrozen(false); 1093 setAdapterInternal(adapter, false, true); 1094 processDataSetCompletelyChanged(false); 1095 requestLayout(); 1096 } 1097 1098 /** 1099 * Removes and recycles all views - both those currently attached, and those in the Recycler. 1100 */ 1101 void removeAndRecycleViews() { 1102 // end all running animations 1103 if (mItemAnimator != null) { 1104 mItemAnimator.endAnimations(); 1105 } 1106 // Since animations are ended, mLayout.children should be equal to 1107 // recyclerView.children. This may not be true if item animator's end does not work as 1108 // expected. (e.g. not release children instantly). It is safer to use mLayout's child 1109 // count. 1110 if (mLayout != null) { 1111 mLayout.removeAndRecycleAllViews(mRecycler); 1112 mLayout.removeAndRecycleScrapInt(mRecycler); 1113 } 1114 // we should clear it here before adapters are swapped to ensure correct callbacks. 1115 mRecycler.clear(); 1116 } 1117 1118 /** 1119 * Replaces the current adapter with the new one and triggers listeners. 1120 * @param adapter The new adapter 1121 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and 1122 * item types with the current adapter (helps us avoid cache 1123 * invalidation). 1124 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If 1125 * compatibleWithPrevious is false, this parameter is ignored. 1126 */ 1127 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, 1128 boolean removeAndRecycleViews) { 1129 if (mAdapter != null) { 1130 mAdapter.unregisterAdapterDataObserver(mObserver); 1131 mAdapter.onDetachedFromRecyclerView(this); 1132 } 1133 if (!compatibleWithPrevious || removeAndRecycleViews) { 1134 removeAndRecycleViews(); 1135 } 1136 mAdapterHelper.reset(); 1137 final Adapter oldAdapter = mAdapter; 1138 mAdapter = adapter; 1139 if (adapter != null) { 1140 adapter.registerAdapterDataObserver(mObserver); 1141 adapter.onAttachedToRecyclerView(this); 1142 } 1143 if (mLayout != null) { 1144 mLayout.onAdapterChanged(oldAdapter, mAdapter); 1145 } 1146 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 1147 mState.mStructureChanged = true; 1148 } 1149 1150 /** 1151 * Retrieves the previously set adapter or null if no adapter is set. 1152 * 1153 * @return The previously set adapter 1154 * @see #setAdapter(Adapter) 1155 */ 1156 @Nullable 1157 public Adapter getAdapter() { 1158 return mAdapter; 1159 } 1160 1161 /** 1162 * Register a listener that will be notified whenever a child view is recycled. 1163 * 1164 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1165 * that a child view is no longer needed. If an application associates expensive 1166 * or heavyweight data with item views, this may be a good place to release 1167 * or free those resources.</p> 1168 * 1169 * @param listener Listener to register, or null to clear 1170 */ 1171 public void setRecyclerListener(@Nullable RecyclerListener listener) { 1172 mRecyclerListener = listener; 1173 } 1174 1175 /** 1176 * <p>Return the offset of the RecyclerView's text baseline from the its top 1177 * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, 1178 * this method returns -1.</p> 1179 * 1180 * @return the offset of the baseline within the RecyclerView's bounds or -1 1181 * if baseline alignment is not supported 1182 */ 1183 @Override 1184 public int getBaseline() { 1185 if (mLayout != null) { 1186 return mLayout.getBaseline(); 1187 } else { 1188 return super.getBaseline(); 1189 } 1190 } 1191 1192 /** 1193 * Register a listener that will be notified whenever a child view is attached to or detached 1194 * from RecyclerView. 1195 * 1196 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1197 * that a child view is no longer needed. If an application associates expensive 1198 * or heavyweight data with item views, this may be a good place to release 1199 * or free those resources.</p> 1200 * 1201 * @param listener Listener to register 1202 */ 1203 public void addOnChildAttachStateChangeListener( 1204 @NonNull OnChildAttachStateChangeListener listener) { 1205 if (mOnChildAttachStateListeners == null) { 1206 mOnChildAttachStateListeners = new ArrayList<>(); 1207 } 1208 mOnChildAttachStateListeners.add(listener); 1209 } 1210 1211 /** 1212 * Removes the provided listener from child attached state listeners list. 1213 * 1214 * @param listener Listener to unregister 1215 */ 1216 public void removeOnChildAttachStateChangeListener( 1217 @NonNull OnChildAttachStateChangeListener listener) { 1218 if (mOnChildAttachStateListeners == null) { 1219 return; 1220 } 1221 mOnChildAttachStateListeners.remove(listener); 1222 } 1223 1224 /** 1225 * Removes all listeners that were added via 1226 * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}. 1227 */ 1228 public void clearOnChildAttachStateChangeListeners() { 1229 if (mOnChildAttachStateListeners != null) { 1230 mOnChildAttachStateListeners.clear(); 1231 } 1232 } 1233 1234 /** 1235 * Set the {@link LayoutManager} that this RecyclerView will use. 1236 * 1237 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 1238 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 1239 * layout arrangements for child views. These arrangements are controlled by the 1240 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 1241 * 1242 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 1243 * 1244 * @param layout LayoutManager to use 1245 */ 1246 public void setLayoutManager(@Nullable LayoutManager layout) { 1247 if (layout == mLayout) { 1248 return; 1249 } 1250 stopScroll(); 1251 // TODO We should do this switch a dispatchLayout pass and animate children. There is a good 1252 // chance that LayoutManagers will re-use views. 1253 if (mLayout != null) { 1254 // end all running animations 1255 if (mItemAnimator != null) { 1256 mItemAnimator.endAnimations(); 1257 } 1258 mLayout.removeAndRecycleAllViews(mRecycler); 1259 mLayout.removeAndRecycleScrapInt(mRecycler); 1260 mRecycler.clear(); 1261 1262 if (mIsAttached) { 1263 mLayout.dispatchDetachedFromWindow(this, mRecycler); 1264 } 1265 mLayout.setRecyclerView(null); 1266 mLayout = null; 1267 } else { 1268 mRecycler.clear(); 1269 } 1270 // this is just a defensive measure for faulty item animators. 1271 mChildHelper.removeAllViewsUnfiltered(); 1272 mLayout = layout; 1273 if (layout != null) { 1274 if (layout.mRecyclerView != null) { 1275 throw new IllegalArgumentException("LayoutManager " + layout 1276 + " is already attached to a RecyclerView:" 1277 + layout.mRecyclerView.exceptionLabel()); 1278 } 1279 mLayout.setRecyclerView(this); 1280 if (mIsAttached) { 1281 mLayout.dispatchAttachedToWindow(this); 1282 } 1283 } 1284 mRecycler.updateViewCacheSize(); 1285 requestLayout(); 1286 } 1287 1288 /** 1289 * Set a {@link OnFlingListener} for this {@link RecyclerView}. 1290 * <p> 1291 * If the {@link OnFlingListener} is set then it will receive 1292 * calls to {@link #fling(int,int)} and will be able to intercept them. 1293 * 1294 * @param onFlingListener The {@link OnFlingListener} instance. 1295 */ 1296 public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { 1297 mOnFlingListener = onFlingListener; 1298 } 1299 1300 /** 1301 * Get the current {@link OnFlingListener} from this {@link RecyclerView}. 1302 * 1303 * @return The {@link OnFlingListener} instance currently set (can be null). 1304 */ 1305 @Nullable 1306 public OnFlingListener getOnFlingListener() { 1307 return mOnFlingListener; 1308 } 1309 1310 @Override 1311 protected Parcelable onSaveInstanceState() { 1312 SavedState state = new SavedState(super.onSaveInstanceState()); 1313 if (mPendingSavedState != null) { 1314 state.copyFrom(mPendingSavedState); 1315 } else if (mLayout != null) { 1316 state.mLayoutState = mLayout.onSaveInstanceState(); 1317 } else { 1318 state.mLayoutState = null; 1319 } 1320 1321 return state; 1322 } 1323 1324 @Override 1325 protected void onRestoreInstanceState(Parcelable state) { 1326 if (!(state instanceof SavedState)) { 1327 super.onRestoreInstanceState(state); 1328 return; 1329 } 1330 1331 mPendingSavedState = (SavedState) state; 1332 super.onRestoreInstanceState(mPendingSavedState.getSuperState()); 1333 if (mLayout != null && mPendingSavedState.mLayoutState != null) { 1334 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); 1335 } 1336 } 1337 1338 /** 1339 * Override to prevent freezing of any views created by the adapter. 1340 */ 1341 @Override 1342 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 1343 dispatchFreezeSelfOnly(container); 1344 } 1345 1346 /** 1347 * Override to prevent thawing of any views created by the adapter. 1348 */ 1349 @Override 1350 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 1351 dispatchThawSelfOnly(container); 1352 } 1353 1354 /** 1355 * Adds a view to the animatingViews list. 1356 * mAnimatingViews holds the child views that are currently being kept around 1357 * purely for the purpose of being animated out of view. They are drawn as a regular 1358 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager 1359 * as they are managed separately from the regular child views. 1360 * @param viewHolder The ViewHolder to be removed 1361 */ 1362 private void addAnimatingView(ViewHolder viewHolder) { 1363 final View view = viewHolder.itemView; 1364 final boolean alreadyParented = view.getParent() == this; 1365 mRecycler.unscrapView(getChildViewHolder(view)); 1366 if (viewHolder.isTmpDetached()) { 1367 // re-attach 1368 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); 1369 } else if (!alreadyParented) { 1370 mChildHelper.addView(view, true); 1371 } else { 1372 mChildHelper.hide(view); 1373 } 1374 } 1375 1376 /** 1377 * Removes a view from the animatingViews list. 1378 * @param view The view to be removed 1379 * @see #addAnimatingView(RecyclerView.ViewHolder) 1380 * @return true if an animating view is removed 1381 */ 1382 boolean removeAnimatingView(View view) { 1383 startInterceptRequestLayout(); 1384 final boolean removed = mChildHelper.removeViewIfHidden(view); 1385 if (removed) { 1386 final ViewHolder viewHolder = getChildViewHolderInt(view); 1387 mRecycler.unscrapView(viewHolder); 1388 mRecycler.recycleViewHolderInternal(viewHolder); 1389 if (DEBUG) { 1390 Log.d(TAG, "after removing animated view: " + view + ", " + this); 1391 } 1392 } 1393 // only clear request eaten flag if we removed the view. 1394 stopInterceptRequestLayout(!removed); 1395 return removed; 1396 } 1397 1398 /** 1399 * Return the {@link LayoutManager} currently responsible for 1400 * layout policy for this RecyclerView. 1401 * 1402 * @return The currently bound LayoutManager 1403 */ 1404 @Nullable 1405 public LayoutManager getLayoutManager() { 1406 return mLayout; 1407 } 1408 1409 /** 1410 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 1411 * if no pool is set for this view a new one will be created. See 1412 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 1413 * 1414 * @return The pool used to store recycled item views for reuse. 1415 * @see #setRecycledViewPool(RecycledViewPool) 1416 */ 1417 @NonNull 1418 public RecycledViewPool getRecycledViewPool() { 1419 return mRecycler.getRecycledViewPool(); 1420 } 1421 1422 /** 1423 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 1424 * This can be useful if you have multiple RecyclerViews with adapters that use the same 1425 * view types, for example if you have several data sets with the same kinds of item views 1426 * displayed by a {@link ViewPager ViewPager}. 1427 * 1428 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 1429 */ 1430 public void setRecycledViewPool(@Nullable RecycledViewPool pool) { 1431 mRecycler.setRecycledViewPool(pool); 1432 } 1433 1434 /** 1435 * Sets a new {@link ViewCacheExtension} to be used by the Recycler. 1436 * 1437 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. 1438 * 1439 * @see ViewCacheExtension#getViewForPositionAndType(Recycler, int, int) 1440 */ 1441 public void setViewCacheExtension(@Nullable ViewCacheExtension extension) { 1442 mRecycler.setViewCacheExtension(extension); 1443 } 1444 1445 /** 1446 * Set the number of offscreen views to retain before adding them to the potentially shared 1447 * {@link #getRecycledViewPool() recycled view pool}. 1448 * 1449 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 1450 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 1451 * to rebind them.</p> 1452 * 1453 * @param size Number of views to cache offscreen before returning them to the general 1454 * recycled view pool 1455 */ 1456 public void setItemViewCacheSize(int size) { 1457 mRecycler.setViewCacheSize(size); 1458 } 1459 1460 /** 1461 * Return the current scrolling state of the RecyclerView. 1462 * 1463 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 1464 * {@link #SCROLL_STATE_SETTLING} 1465 */ 1466 public int getScrollState() { 1467 return mScrollState; 1468 } 1469 1470 void setScrollState(int state) { 1471 if (state == mScrollState) { 1472 return; 1473 } 1474 if (DEBUG) { 1475 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, 1476 new Exception()); 1477 } 1478 mScrollState = state; 1479 if (state != SCROLL_STATE_SETTLING) { 1480 stopScrollersInternal(); 1481 } 1482 dispatchOnScrollStateChanged(state); 1483 } 1484 1485 /** 1486 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1487 * affect both measurement and drawing of individual item views. 1488 * 1489 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1490 * be run/queried/drawn first for their effects on item views. Padding added to views 1491 * will be nested; a padding added by an earlier decoration will mean further 1492 * item decorations in the list will be asked to draw/pad within the previous decoration's 1493 * given area.</p> 1494 * 1495 * @param decor Decoration to add 1496 * @param index Position in the decoration chain to insert this decoration at. If this value 1497 * is negative the decoration will be added at the end. 1498 */ 1499 public void addItemDecoration(@NonNull ItemDecoration decor, int index) { 1500 if (mLayout != null) { 1501 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" 1502 + " layout"); 1503 } 1504 if (mItemDecorations.isEmpty()) { 1505 setWillNotDraw(false); 1506 } 1507 if (index < 0) { 1508 mItemDecorations.add(decor); 1509 } else { 1510 mItemDecorations.add(index, decor); 1511 } 1512 markItemDecorInsetsDirty(); 1513 requestLayout(); 1514 } 1515 1516 /** 1517 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1518 * affect both measurement and drawing of individual item views. 1519 * 1520 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1521 * be run/queried/drawn first for their effects on item views. Padding added to views 1522 * will be nested; a padding added by an earlier decoration will mean further 1523 * item decorations in the list will be asked to draw/pad within the previous decoration's 1524 * given area.</p> 1525 * 1526 * @param decor Decoration to add 1527 */ 1528 public void addItemDecoration(@NonNull ItemDecoration decor) { 1529 addItemDecoration(decor, -1); 1530 } 1531 1532 /** 1533 * Returns an {@link ItemDecoration} previously added to this RecyclerView. 1534 * 1535 * @param index The index position of the desired ItemDecoration. 1536 * @return the ItemDecoration at index position 1537 * @throws IndexOutOfBoundsException on invalid index 1538 */ 1539 @NonNull 1540 public ItemDecoration getItemDecorationAt(int index) { 1541 final int size = getItemDecorationCount(); 1542 if (index < 0 || index >= size) { 1543 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1544 } 1545 1546 return mItemDecorations.get(index); 1547 } 1548 1549 /** 1550 * Returns the number of {@link ItemDecoration} currently added to this RecyclerView. 1551 * 1552 * @return number of ItemDecorations currently added added to this RecyclerView. 1553 */ 1554 public int getItemDecorationCount() { 1555 return mItemDecorations.size(); 1556 } 1557 1558 /** 1559 * Removes the {@link ItemDecoration} associated with the supplied index position. 1560 * 1561 * @param index The index position of the ItemDecoration to be removed. 1562 */ 1563 public void removeItemDecorationAt(int index) { 1564 final int size = getItemDecorationCount(); 1565 if (index < 0 || index >= size) { 1566 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1567 } 1568 1569 removeItemDecoration(getItemDecorationAt(index)); 1570 } 1571 1572 /** 1573 * Remove an {@link ItemDecoration} from this RecyclerView. 1574 * 1575 * <p>The given decoration will no longer impact the measurement and drawing of 1576 * item views.</p> 1577 * 1578 * @param decor Decoration to remove 1579 * @see #addItemDecoration(ItemDecoration) 1580 */ 1581 public void removeItemDecoration(@NonNull ItemDecoration decor) { 1582 if (mLayout != null) { 1583 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" 1584 + " layout"); 1585 } 1586 mItemDecorations.remove(decor); 1587 if (mItemDecorations.isEmpty()) { 1588 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 1589 } 1590 markItemDecorInsetsDirty(); 1591 requestLayout(); 1592 } 1593 1594 /** 1595 * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children. 1596 * <p> 1597 * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will 1598 * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be 1599 * true if childDrawingOrderCallback is not null, false otherwise. 1600 * <p> 1601 * Note that child drawing order may be overridden by View's elevation. 1602 * 1603 * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing 1604 * system. 1605 */ 1606 public void setChildDrawingOrderCallback( 1607 @Nullable ChildDrawingOrderCallback childDrawingOrderCallback) { 1608 if (childDrawingOrderCallback == mChildDrawingOrderCallback) { 1609 return; 1610 } 1611 mChildDrawingOrderCallback = childDrawingOrderCallback; 1612 setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null); 1613 } 1614 1615 /** 1616 * Set a listener that will be notified of any changes in scroll state or position. 1617 * 1618 * @param listener Listener to set or null to clear 1619 * 1620 * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and 1621 * {@link #removeOnScrollListener(OnScrollListener)} 1622 */ 1623 @Deprecated 1624 public void setOnScrollListener(@Nullable OnScrollListener listener) { 1625 mScrollListener = listener; 1626 } 1627 1628 /** 1629 * Add a listener that will be notified of any changes in scroll state or position. 1630 * 1631 * <p>Components that add a listener should take care to remove it when finished. 1632 * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} 1633 * to remove all attached listeners.</p> 1634 * 1635 * @param listener listener to set 1636 */ 1637 public void addOnScrollListener(@NonNull OnScrollListener listener) { 1638 if (mScrollListeners == null) { 1639 mScrollListeners = new ArrayList<>(); 1640 } 1641 mScrollListeners.add(listener); 1642 } 1643 1644 /** 1645 * Remove a listener that was notified of any changes in scroll state or position. 1646 * 1647 * @param listener listener to set or null to clear 1648 */ 1649 public void removeOnScrollListener(@NonNull OnScrollListener listener) { 1650 if (mScrollListeners != null) { 1651 mScrollListeners.remove(listener); 1652 } 1653 } 1654 1655 /** 1656 * Remove all secondary listener that were notified of any changes in scroll state or position. 1657 */ 1658 public void clearOnScrollListeners() { 1659 if (mScrollListeners != null) { 1660 mScrollListeners.clear(); 1661 } 1662 } 1663 1664 /** 1665 * Convenience method to scroll to a certain position. 1666 * 1667 * RecyclerView does not implement scrolling logic, rather forwards the call to 1668 * {@link RecyclerView.LayoutManager#scrollToPosition(int)} 1669 * @param position Scroll to this adapter position 1670 * @see RecyclerView.LayoutManager#scrollToPosition(int) 1671 */ 1672 public void scrollToPosition(int position) { 1673 if (mLayoutFrozen) { 1674 return; 1675 } 1676 stopScroll(); 1677 if (mLayout == null) { 1678 Log.e(TAG, "Cannot scroll to position a LayoutManager set. " 1679 + "Call setLayoutManager with a non-null argument."); 1680 return; 1681 } 1682 mLayout.scrollToPosition(position); 1683 awakenScrollBars(); 1684 } 1685 1686 void jumpToPositionForSmoothScroller(int position) { 1687 if (mLayout == null) { 1688 return; 1689 } 1690 mLayout.scrollToPosition(position); 1691 awakenScrollBars(); 1692 } 1693 1694 /** 1695 * Starts a smooth scroll to an adapter position. 1696 * <p> 1697 * To support smooth scrolling, you must override 1698 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a 1699 * {@link SmoothScroller}. 1700 * <p> 1701 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to 1702 * provide a custom smooth scroll logic, override 1703 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your 1704 * LayoutManager. 1705 * 1706 * @param position The adapter position to scroll to 1707 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) 1708 */ 1709 public void smoothScrollToPosition(int position) { 1710 if (mLayoutFrozen) { 1711 return; 1712 } 1713 if (mLayout == null) { 1714 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 1715 + "Call setLayoutManager with a non-null argument."); 1716 return; 1717 } 1718 mLayout.smoothScrollToPosition(this, mState, position); 1719 } 1720 1721 @Override 1722 public void scrollTo(int x, int y) { 1723 Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " 1724 + "Use scrollToPosition instead"); 1725 } 1726 1727 @Override 1728 public void scrollBy(int x, int y) { 1729 if (mLayout == null) { 1730 Log.e(TAG, "Cannot scroll without a LayoutManager set. " 1731 + "Call setLayoutManager with a non-null argument."); 1732 return; 1733 } 1734 if (mLayoutFrozen) { 1735 return; 1736 } 1737 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1738 final boolean canScrollVertical = mLayout.canScrollVertically(); 1739 if (canScrollHorizontal || canScrollVertical) { 1740 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null); 1741 } 1742 } 1743 1744 /** 1745 * Scrolls the RV by 'dx' and 'dy' via calls to 1746 * {@link LayoutManager#scrollHorizontallyBy(int, Recycler, State)} and 1747 * {@link LayoutManager#scrollVerticallyBy(int, Recycler, State)}. 1748 * 1749 * Also sets how much of the scroll was actually consumed in 'consumed' parameter (indexes 0 and 1750 * 1 for the x axis and y axis, respectively). 1751 * 1752 * This method should only be called in the context of an existing scroll operation such that 1753 * any other necessary operations (such as a call to {@link #consumePendingUpdateOperations()}) 1754 * is already handled. 1755 */ 1756 private void scrollStep(int dx, int dy, @Nullable int[] consumed) { 1757 startInterceptRequestLayout(); 1758 onEnterLayoutOrScroll(); 1759 1760 TraceCompat.beginSection(TRACE_SCROLL_TAG); 1761 fillRemainingScrollValues(mState); 1762 1763 int consumedX = 0; 1764 int consumedY = 0; 1765 if (dx != 0) { 1766 consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); 1767 } 1768 if (dy != 0) { 1769 consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); 1770 } 1771 1772 TraceCompat.endSection(); 1773 repositionShadowingViews(); 1774 1775 onExitLayoutOrScroll(); 1776 stopInterceptRequestLayout(false); 1777 1778 if (consumed != null) { 1779 consumed[0] = consumedX; 1780 consumed[1] = consumedY; 1781 } 1782 } 1783 1784 /** 1785 * Helper method reflect data changes to the state. 1786 * <p> 1787 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change 1788 * but data actually changed. 1789 * <p> 1790 * This method consumes all deferred changes to avoid that case. 1791 */ 1792 void consumePendingUpdateOperations() { 1793 if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { 1794 TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 1795 dispatchLayout(); 1796 TraceCompat.endSection(); 1797 return; 1798 } 1799 if (!mAdapterHelper.hasPendingUpdates()) { 1800 return; 1801 } 1802 1803 // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any 1804 // of the visible items is affected and if not, just ignore the change. 1805 if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper 1806 .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE 1807 | AdapterHelper.UpdateOp.MOVE)) { 1808 TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); 1809 startInterceptRequestLayout(); 1810 onEnterLayoutOrScroll(); 1811 mAdapterHelper.preProcess(); 1812 if (!mLayoutWasDefered) { 1813 if (hasUpdatedView()) { 1814 dispatchLayout(); 1815 } else { 1816 // no need to layout, clean state 1817 mAdapterHelper.consumePostponedUpdates(); 1818 } 1819 } 1820 stopInterceptRequestLayout(true); 1821 onExitLayoutOrScroll(); 1822 TraceCompat.endSection(); 1823 } else if (mAdapterHelper.hasPendingUpdates()) { 1824 TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 1825 dispatchLayout(); 1826 TraceCompat.endSection(); 1827 } 1828 } 1829 1830 /** 1831 * @return True if an existing view holder needs to be updated 1832 */ 1833 private boolean hasUpdatedView() { 1834 final int childCount = mChildHelper.getChildCount(); 1835 for (int i = 0; i < childCount; i++) { 1836 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 1837 if (holder == null || holder.shouldIgnore()) { 1838 continue; 1839 } 1840 if (holder.isUpdated()) { 1841 return true; 1842 } 1843 } 1844 return false; 1845 } 1846 1847 /** 1848 * Does not perform bounds checking. Used by internal methods that have already validated input. 1849 * <p> 1850 * It also reports any unused scroll request to the related EdgeEffect. 1851 * 1852 * @param x The amount of horizontal scroll request 1853 * @param y The amount of vertical scroll request 1854 * @param ev The originating MotionEvent, or null if not from a touch event. 1855 * 1856 * @return Whether any scroll was consumed in either direction. 1857 */ 1858 boolean scrollByInternal(int x, int y, MotionEvent ev) { 1859 int unconsumedX = 0, unconsumedY = 0; 1860 int consumedX = 0, consumedY = 0; 1861 1862 consumePendingUpdateOperations(); 1863 if (mAdapter != null) { 1864 scrollStep(x, y, mScrollStepConsumed); 1865 consumedX = mScrollStepConsumed[0]; 1866 consumedY = mScrollStepConsumed[1]; 1867 unconsumedX = x - consumedX; 1868 unconsumedY = y - consumedY; 1869 } 1870 if (!mItemDecorations.isEmpty()) { 1871 invalidate(); 1872 } 1873 1874 if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, 1875 TYPE_TOUCH)) { 1876 // Update the last touch co-ords, taking any scroll offset into account 1877 mLastTouchX -= mScrollOffset[0]; 1878 mLastTouchY -= mScrollOffset[1]; 1879 if (ev != null) { 1880 ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 1881 } 1882 mNestedOffsets[0] += mScrollOffset[0]; 1883 mNestedOffsets[1] += mScrollOffset[1]; 1884 } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 1885 if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) { 1886 pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); 1887 } 1888 considerReleasingGlowsOnScroll(x, y); 1889 } 1890 if (consumedX != 0 || consumedY != 0) { 1891 dispatchOnScrolled(consumedX, consumedY); 1892 } 1893 if (!awakenScrollBars()) { 1894 invalidate(); 1895 } 1896 return consumedX != 0 || consumedY != 0; 1897 } 1898 1899 /** 1900 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal 1901 * range. This value is used to compute the length of the thumb within the scrollbar's track. 1902 * </p> 1903 * 1904 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1905 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> 1906 * 1907 * <p>Default implementation returns 0.</p> 1908 * 1909 * <p>If you want to support scroll bars, override 1910 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your 1911 * LayoutManager. </p> 1912 * 1913 * @return The horizontal offset of the scrollbar's thumb 1914 * @see RecyclerView.LayoutManager#computeHorizontalScrollOffset 1915 * (RecyclerView.State) 1916 */ 1917 @Override 1918 public int computeHorizontalScrollOffset() { 1919 if (mLayout == null) { 1920 return 0; 1921 } 1922 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; 1923 } 1924 1925 /** 1926 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the 1927 * horizontal range. This value is used to compute the length of the thumb within the 1928 * scrollbar's track.</p> 1929 * 1930 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1931 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> 1932 * 1933 * <p>Default implementation returns 0.</p> 1934 * 1935 * <p>If you want to support scroll bars, override 1936 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your 1937 * LayoutManager.</p> 1938 * 1939 * @return The horizontal extent of the scrollbar's thumb 1940 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) 1941 */ 1942 @Override 1943 public int computeHorizontalScrollExtent() { 1944 if (mLayout == null) { 1945 return 0; 1946 } 1947 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; 1948 } 1949 1950 /** 1951 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> 1952 * 1953 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1954 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> 1955 * 1956 * <p>Default implementation returns 0.</p> 1957 * 1958 * <p>If you want to support scroll bars, override 1959 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your 1960 * LayoutManager.</p> 1961 * 1962 * @return The total horizontal range represented by the vertical scrollbar 1963 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) 1964 */ 1965 @Override 1966 public int computeHorizontalScrollRange() { 1967 if (mLayout == null) { 1968 return 0; 1969 } 1970 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; 1971 } 1972 1973 /** 1974 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. 1975 * This value is used to compute the length of the thumb within the scrollbar's track. </p> 1976 * 1977 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1978 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> 1979 * 1980 * <p>Default implementation returns 0.</p> 1981 * 1982 * <p>If you want to support scroll bars, override 1983 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your 1984 * LayoutManager.</p> 1985 * 1986 * @return The vertical offset of the scrollbar's thumb 1987 * @see RecyclerView.LayoutManager#computeVerticalScrollOffset 1988 * (RecyclerView.State) 1989 */ 1990 @Override 1991 public int computeVerticalScrollOffset() { 1992 if (mLayout == null) { 1993 return 0; 1994 } 1995 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; 1996 } 1997 1998 /** 1999 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. 2000 * This value is used to compute the length of the thumb within the scrollbar's track.</p> 2001 * 2002 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2003 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> 2004 * 2005 * <p>Default implementation returns 0.</p> 2006 * 2007 * <p>If you want to support scroll bars, override 2008 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your 2009 * LayoutManager.</p> 2010 * 2011 * @return The vertical extent of the scrollbar's thumb 2012 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) 2013 */ 2014 @Override 2015 public int computeVerticalScrollExtent() { 2016 if (mLayout == null) { 2017 return 0; 2018 } 2019 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; 2020 } 2021 2022 /** 2023 * <p>Compute the vertical range that the vertical scrollbar represents.</p> 2024 * 2025 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2026 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> 2027 * 2028 * <p>Default implementation returns 0.</p> 2029 * 2030 * <p>If you want to support scroll bars, override 2031 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your 2032 * LayoutManager.</p> 2033 * 2034 * @return The total vertical range represented by the vertical scrollbar 2035 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) 2036 */ 2037 @Override 2038 public int computeVerticalScrollRange() { 2039 if (mLayout == null) { 2040 return 0; 2041 } 2042 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; 2043 } 2044 2045 /** 2046 * This method should be called before any code that may trigger a child view to cause a call to 2047 * {@link RecyclerView#requestLayout()}. Doing so enables {@link RecyclerView} to avoid 2048 * reacting to additional redundant calls to {@link #requestLayout()}. 2049 * <p> 2050 * A call to this method must always be accompanied by a call to 2051 * {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a 2052 * child View to cause a call to {@link RecyclerView#requestLayout()}. 2053 * 2054 * @see #stopInterceptRequestLayout(boolean) 2055 */ 2056 void startInterceptRequestLayout() { 2057 mInterceptRequestLayoutDepth++; 2058 if (mInterceptRequestLayoutDepth == 1 && !mLayoutFrozen) { 2059 mLayoutWasDefered = false; 2060 } 2061 } 2062 2063 /** 2064 * This method should be called after any code that may trigger a child view to cause a call to 2065 * {@link RecyclerView#requestLayout()}. 2066 * <p> 2067 * A call to this method must always be accompanied by a call to 2068 * {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child 2069 * View to cause a call to {@link RecyclerView#requestLayout()}. 2070 * 2071 * @see #startInterceptRequestLayout() 2072 */ 2073 void stopInterceptRequestLayout(boolean performLayoutChildren) { 2074 if (mInterceptRequestLayoutDepth < 1) { 2075 //noinspection PointlessBooleanExpression 2076 if (DEBUG) { 2077 throw new IllegalStateException("stopInterceptRequestLayout was called more " 2078 + "times than startInterceptRequestLayout." 2079 + exceptionLabel()); 2080 } 2081 mInterceptRequestLayoutDepth = 1; 2082 } 2083 if (!performLayoutChildren && !mLayoutFrozen) { 2084 // Reset the layout request eaten counter. 2085 // This is necessary since eatRequest calls can be nested in which case the other 2086 // call will override the inner one. 2087 // for instance: 2088 // eat layout for process adapter updates 2089 // eat layout for dispatchLayout 2090 // a bunch of req layout calls arrive 2091 2092 mLayoutWasDefered = false; 2093 } 2094 if (mInterceptRequestLayoutDepth == 1) { 2095 // when layout is frozen we should delay dispatchLayout() 2096 if (performLayoutChildren && mLayoutWasDefered && !mLayoutFrozen 2097 && mLayout != null && mAdapter != null) { 2098 dispatchLayout(); 2099 } 2100 if (!mLayoutFrozen) { 2101 mLayoutWasDefered = false; 2102 } 2103 } 2104 mInterceptRequestLayoutDepth--; 2105 } 2106 2107 /** 2108 * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called, 2109 * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called; 2110 * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)}, 2111 * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and 2112 * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are 2113 * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be 2114 * called. 2115 * 2116 * <p> 2117 * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link 2118 * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( 2119 * RecyclerView, State, int)}. 2120 * <p> 2121 * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically 2122 * stop frozen. 2123 * <p> 2124 * Note: Running ItemAnimator is not stopped automatically, it's caller's 2125 * responsibility to call ItemAnimator.end(). 2126 * 2127 * @param frozen true to freeze layout and scroll, false to re-enable. 2128 */ 2129 public void setLayoutFrozen(boolean frozen) { 2130 if (frozen != mLayoutFrozen) { 2131 assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"); 2132 if (!frozen) { 2133 mLayoutFrozen = false; 2134 if (mLayoutWasDefered && mLayout != null && mAdapter != null) { 2135 requestLayout(); 2136 } 2137 mLayoutWasDefered = false; 2138 } else { 2139 final long now = SystemClock.uptimeMillis(); 2140 MotionEvent cancelEvent = MotionEvent.obtain(now, now, 2141 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 2142 onTouchEvent(cancelEvent); 2143 mLayoutFrozen = true; 2144 mIgnoreMotionEventTillDown = true; 2145 stopScroll(); 2146 } 2147 } 2148 } 2149 2150 /** 2151 * Returns true if layout and scroll are frozen. 2152 * 2153 * @return true if layout and scroll are frozen 2154 * @see #setLayoutFrozen(boolean) 2155 */ 2156 public boolean isLayoutFrozen() { 2157 return mLayoutFrozen; 2158 } 2159 2160 /** 2161 * Animate a scroll by the given amount of pixels along either axis. 2162 * 2163 * @param dx Pixels to scroll horizontally 2164 * @param dy Pixels to scroll vertically 2165 */ 2166 public void smoothScrollBy(@Px int dx, @Px int dy) { 2167 smoothScrollBy(dx, dy, null); 2168 } 2169 2170 /** 2171 * Animate a scroll by the given amount of pixels along either axis. 2172 * 2173 * @param dx Pixels to scroll horizontally 2174 * @param dy Pixels to scroll vertically 2175 * @param interpolator {@link Interpolator} to be used for scrolling. If it is 2176 * {@code null}, RecyclerView is going to use the default interpolator. 2177 */ 2178 public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) { 2179 if (mLayout == null) { 2180 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 2181 + "Call setLayoutManager with a non-null argument."); 2182 return; 2183 } 2184 if (mLayoutFrozen) { 2185 return; 2186 } 2187 if (!mLayout.canScrollHorizontally()) { 2188 dx = 0; 2189 } 2190 if (!mLayout.canScrollVertically()) { 2191 dy = 0; 2192 } 2193 if (dx != 0 || dy != 0) { 2194 mViewFlinger.smoothScrollBy(dx, dy, interpolator); 2195 } 2196 } 2197 2198 /** 2199 * Begin a standard fling with an initial velocity along each axis in pixels per second. 2200 * If the velocity given is below the system-defined minimum this method will return false 2201 * and no fling will occur. 2202 * 2203 * @param velocityX Initial horizontal velocity in pixels per second 2204 * @param velocityY Initial vertical velocity in pixels per second 2205 * @return true if the fling was started, false if the velocity was too low to fling or 2206 * LayoutManager does not support scrolling in the axis fling is issued. 2207 * 2208 * @see LayoutManager#canScrollVertically() 2209 * @see LayoutManager#canScrollHorizontally() 2210 */ 2211 public boolean fling(int velocityX, int velocityY) { 2212 if (mLayout == null) { 2213 Log.e(TAG, "Cannot fling without a LayoutManager set. " 2214 + "Call setLayoutManager with a non-null argument."); 2215 return false; 2216 } 2217 if (mLayoutFrozen) { 2218 return false; 2219 } 2220 2221 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2222 final boolean canScrollVertical = mLayout.canScrollVertically(); 2223 2224 if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { 2225 velocityX = 0; 2226 } 2227 if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { 2228 velocityY = 0; 2229 } 2230 if (velocityX == 0 && velocityY == 0) { 2231 // If we don't have any velocity, return false 2232 return false; 2233 } 2234 2235 if (!dispatchNestedPreFling(velocityX, velocityY)) { 2236 final boolean canScroll = canScrollHorizontal || canScrollVertical; 2237 dispatchNestedFling(velocityX, velocityY, canScroll); 2238 2239 if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { 2240 return true; 2241 } 2242 2243 if (canScroll) { 2244 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2245 if (canScrollHorizontal) { 2246 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2247 } 2248 if (canScrollVertical) { 2249 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2250 } 2251 startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH); 2252 2253 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 2254 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 2255 mViewFlinger.fling(velocityX, velocityY); 2256 return true; 2257 } 2258 } 2259 return false; 2260 } 2261 2262 /** 2263 * Stop any current scroll in progress, such as one started by 2264 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 2265 */ 2266 public void stopScroll() { 2267 setScrollState(SCROLL_STATE_IDLE); 2268 stopScrollersInternal(); 2269 } 2270 2271 /** 2272 * Similar to {@link #stopScroll()} but does not set the state. 2273 */ 2274 private void stopScrollersInternal() { 2275 mViewFlinger.stop(); 2276 if (mLayout != null) { 2277 mLayout.stopSmoothScroller(); 2278 } 2279 } 2280 2281 /** 2282 * Returns the minimum velocity to start a fling. 2283 * 2284 * @return The minimum velocity to start a fling 2285 */ 2286 public int getMinFlingVelocity() { 2287 return mMinFlingVelocity; 2288 } 2289 2290 2291 /** 2292 * Returns the maximum fling velocity used by this RecyclerView. 2293 * 2294 * @return The maximum fling velocity used by this RecyclerView. 2295 */ 2296 public int getMaxFlingVelocity() { 2297 return mMaxFlingVelocity; 2298 } 2299 2300 /** 2301 * Apply a pull to relevant overscroll glow effects 2302 */ 2303 private void pullGlows(float x, float overscrollX, float y, float overscrollY) { 2304 boolean invalidate = false; 2305 if (overscrollX < 0) { 2306 ensureLeftGlow(); 2307 EdgeEffectCompat.onPull(mLeftGlow, -overscrollX / getWidth(), 1f - y / getHeight()); 2308 invalidate = true; 2309 } else if (overscrollX > 0) { 2310 ensureRightGlow(); 2311 EdgeEffectCompat.onPull(mRightGlow, overscrollX / getWidth(), y / getHeight()); 2312 invalidate = true; 2313 } 2314 2315 if (overscrollY < 0) { 2316 ensureTopGlow(); 2317 EdgeEffectCompat.onPull(mTopGlow, -overscrollY / getHeight(), x / getWidth()); 2318 invalidate = true; 2319 } else if (overscrollY > 0) { 2320 ensureBottomGlow(); 2321 EdgeEffectCompat.onPull(mBottomGlow, overscrollY / getHeight(), 1f - x / getWidth()); 2322 invalidate = true; 2323 } 2324 2325 if (invalidate || overscrollX != 0 || overscrollY != 0) { 2326 ViewCompat.postInvalidateOnAnimation(this); 2327 } 2328 } 2329 2330 private void releaseGlows() { 2331 boolean needsInvalidate = false; 2332 if (mLeftGlow != null) { 2333 mLeftGlow.onRelease(); 2334 needsInvalidate = mLeftGlow.isFinished(); 2335 } 2336 if (mTopGlow != null) { 2337 mTopGlow.onRelease(); 2338 needsInvalidate |= mTopGlow.isFinished(); 2339 } 2340 if (mRightGlow != null) { 2341 mRightGlow.onRelease(); 2342 needsInvalidate |= mRightGlow.isFinished(); 2343 } 2344 if (mBottomGlow != null) { 2345 mBottomGlow.onRelease(); 2346 needsInvalidate |= mBottomGlow.isFinished(); 2347 } 2348 if (needsInvalidate) { 2349 ViewCompat.postInvalidateOnAnimation(this); 2350 } 2351 } 2352 2353 void considerReleasingGlowsOnScroll(int dx, int dy) { 2354 boolean needsInvalidate = false; 2355 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { 2356 mLeftGlow.onRelease(); 2357 needsInvalidate = mLeftGlow.isFinished(); 2358 } 2359 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { 2360 mRightGlow.onRelease(); 2361 needsInvalidate |= mRightGlow.isFinished(); 2362 } 2363 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { 2364 mTopGlow.onRelease(); 2365 needsInvalidate |= mTopGlow.isFinished(); 2366 } 2367 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { 2368 mBottomGlow.onRelease(); 2369 needsInvalidate |= mBottomGlow.isFinished(); 2370 } 2371 if (needsInvalidate) { 2372 ViewCompat.postInvalidateOnAnimation(this); 2373 } 2374 } 2375 2376 void absorbGlows(int velocityX, int velocityY) { 2377 if (velocityX < 0) { 2378 ensureLeftGlow(); 2379 mLeftGlow.onAbsorb(-velocityX); 2380 } else if (velocityX > 0) { 2381 ensureRightGlow(); 2382 mRightGlow.onAbsorb(velocityX); 2383 } 2384 2385 if (velocityY < 0) { 2386 ensureTopGlow(); 2387 mTopGlow.onAbsorb(-velocityY); 2388 } else if (velocityY > 0) { 2389 ensureBottomGlow(); 2390 mBottomGlow.onAbsorb(velocityY); 2391 } 2392 2393 if (velocityX != 0 || velocityY != 0) { 2394 ViewCompat.postInvalidateOnAnimation(this); 2395 } 2396 } 2397 2398 void ensureLeftGlow() { 2399 if (mLeftGlow != null) { 2400 return; 2401 } 2402 mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT); 2403 if (mClipToPadding) { 2404 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 2405 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 2406 } else { 2407 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 2408 } 2409 } 2410 2411 void ensureRightGlow() { 2412 if (mRightGlow != null) { 2413 return; 2414 } 2415 mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT); 2416 if (mClipToPadding) { 2417 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 2418 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 2419 } else { 2420 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 2421 } 2422 } 2423 2424 void ensureTopGlow() { 2425 if (mTopGlow != null) { 2426 return; 2427 } 2428 mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP); 2429 if (mClipToPadding) { 2430 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 2431 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 2432 } else { 2433 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 2434 } 2435 2436 } 2437 2438 void ensureBottomGlow() { 2439 if (mBottomGlow != null) { 2440 return; 2441 } 2442 mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM); 2443 if (mClipToPadding) { 2444 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 2445 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 2446 } else { 2447 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 2448 } 2449 } 2450 2451 void invalidateGlows() { 2452 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; 2453 } 2454 2455 /** 2456 * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}. 2457 * <p> 2458 * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared 2459 * and new effects are created as needed using 2460 * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)} 2461 * 2462 * @param edgeEffectFactory The {@link EdgeEffectFactory} instance. 2463 */ 2464 public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) { 2465 Preconditions.checkNotNull(edgeEffectFactory); 2466 mEdgeEffectFactory = edgeEffectFactory; 2467 invalidateGlows(); 2468 } 2469 2470 /** 2471 * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing 2472 * was set. 2473 * 2474 * @return The previously set {@link EdgeEffectFactory} 2475 * @see #setEdgeEffectFactory(EdgeEffectFactory) 2476 */ 2477 @NonNull 2478 public EdgeEffectFactory getEdgeEffectFactory() { 2479 return mEdgeEffectFactory; 2480 } 2481 2482 /** 2483 * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are 2484 * in the Adapter but not visible in the UI), it employs a more involved focus search strategy 2485 * that differs from other ViewGroups. 2486 * <p> 2487 * It first does a focus search within the RecyclerView. If this search finds a View that is in 2488 * the focus direction with respect to the currently focused View, RecyclerView returns that 2489 * child as the next focus target. When it cannot find such child, it calls 2490 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views 2491 * in the focus search direction. If LayoutManager adds a View that matches the 2492 * focus search criteria, it will be returned as the focus search result. Otherwise, 2493 * RecyclerView will call parent to handle the focus search like a regular ViewGroup. 2494 * <p> 2495 * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that 2496 * is not in the focus direction is still valid focus target which may not be the desired 2497 * behavior if the Adapter has more children in the focus direction. To handle this case, 2498 * RecyclerView converts the focus direction to an absolute direction and makes a preliminary 2499 * focus search in that direction. If there are no Views to gain focus, it will call 2500 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a 2501 * focus search with the original (relative) direction. This allows RecyclerView to provide 2502 * better candidates to the focus search while still allowing the view system to take focus from 2503 * the RecyclerView and give it to a more suitable child if such child exists. 2504 * 2505 * @param focused The view that currently has focus 2506 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 2507 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}, 2508 * {@link View#FOCUS_BACKWARD} or 0 for not applicable. 2509 * 2510 * @return A new View that can be the next focus after the focused View 2511 */ 2512 @Override 2513 public View focusSearch(View focused, int direction) { 2514 View result = mLayout.onInterceptFocusSearch(focused, direction); 2515 if (result != null) { 2516 return result; 2517 } 2518 final boolean canRunFocusFailure = mAdapter != null && mLayout != null 2519 && !isComputingLayout() && !mLayoutFrozen; 2520 2521 final FocusFinder ff = FocusFinder.getInstance(); 2522 if (canRunFocusFailure 2523 && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { 2524 // convert direction to absolute direction and see if we have a view there and if not 2525 // tell LayoutManager to add if it can. 2526 boolean needsFocusFailureLayout = false; 2527 if (mLayout.canScrollVertically()) { 2528 final int absDir = 2529 direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; 2530 final View found = ff.findNextFocus(this, focused, absDir); 2531 needsFocusFailureLayout = found == null; 2532 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { 2533 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. 2534 direction = absDir; 2535 } 2536 } 2537 if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { 2538 boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; 2539 final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl 2540 ? View.FOCUS_RIGHT : View.FOCUS_LEFT; 2541 final View found = ff.findNextFocus(this, focused, absDir); 2542 needsFocusFailureLayout = found == null; 2543 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { 2544 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. 2545 direction = absDir; 2546 } 2547 } 2548 if (needsFocusFailureLayout) { 2549 consumePendingUpdateOperations(); 2550 final View focusedItemView = findContainingItemView(focused); 2551 if (focusedItemView == null) { 2552 // panic, focused view is not a child anymore, cannot call super. 2553 return null; 2554 } 2555 startInterceptRequestLayout(); 2556 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 2557 stopInterceptRequestLayout(false); 2558 } 2559 result = ff.findNextFocus(this, focused, direction); 2560 } else { 2561 result = ff.findNextFocus(this, focused, direction); 2562 if (result == null && canRunFocusFailure) { 2563 consumePendingUpdateOperations(); 2564 final View focusedItemView = findContainingItemView(focused); 2565 if (focusedItemView == null) { 2566 // panic, focused view is not a child anymore, cannot call super. 2567 return null; 2568 } 2569 startInterceptRequestLayout(); 2570 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 2571 stopInterceptRequestLayout(false); 2572 } 2573 } 2574 if (result != null && !result.hasFocusable()) { 2575 if (getFocusedChild() == null) { 2576 // Scrolling to this unfocusable view is not meaningful since there is no currently 2577 // focused view which RV needs to keep visible. 2578 return super.focusSearch(focused, direction); 2579 } 2580 // If the next view returned by onFocusSearchFailed in layout manager has no focusable 2581 // views, we still scroll to that view in order to make it visible on the screen. 2582 // If it's focusable, framework already calls RV's requestChildFocus which handles 2583 // bringing this newly focused item onto the screen. 2584 requestChildOnScreen(result, null); 2585 return focused; 2586 } 2587 return isPreferredNextFocus(focused, result, direction) 2588 ? result : super.focusSearch(focused, direction); 2589 } 2590 2591 /** 2592 * Checks if the new focus candidate is a good enough candidate such that RecyclerView will 2593 * assign it as the next focus View instead of letting view hierarchy decide. 2594 * A good candidate means a View that is aligned in the focus direction wrt the focused View 2595 * and is not the RecyclerView itself. 2596 * When this method returns false, RecyclerView will let the parent make the decision so the 2597 * same View may still get the focus as a result of that search. 2598 */ 2599 private boolean isPreferredNextFocus(View focused, View next, int direction) { 2600 if (next == null || next == this) { 2601 return false; 2602 } 2603 // panic, result view is not a child anymore, maybe workaround b/37864393 2604 if (findContainingItemView(next) == null) { 2605 return false; 2606 } 2607 if (focused == null) { 2608 return true; 2609 } 2610 // panic, focused view is not a child anymore, maybe workaround b/37864393 2611 if (findContainingItemView(focused) == null) { 2612 return true; 2613 } 2614 2615 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 2616 mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); 2617 offsetDescendantRectToMyCoords(focused, mTempRect); 2618 offsetDescendantRectToMyCoords(next, mTempRect2); 2619 final int rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL ? -1 : 1; 2620 int rightness = 0; 2621 if ((mTempRect.left < mTempRect2.left 2622 || mTempRect.right <= mTempRect2.left) 2623 && mTempRect.right < mTempRect2.right) { 2624 rightness = 1; 2625 } else if ((mTempRect.right > mTempRect2.right 2626 || mTempRect.left >= mTempRect2.right) 2627 && mTempRect.left > mTempRect2.left) { 2628 rightness = -1; 2629 } 2630 int downness = 0; 2631 if ((mTempRect.top < mTempRect2.top 2632 || mTempRect.bottom <= mTempRect2.top) 2633 && mTempRect.bottom < mTempRect2.bottom) { 2634 downness = 1; 2635 } else if ((mTempRect.bottom > mTempRect2.bottom 2636 || mTempRect.top >= mTempRect2.bottom) 2637 && mTempRect.top > mTempRect2.top) { 2638 downness = -1; 2639 } 2640 switch (direction) { 2641 case View.FOCUS_LEFT: 2642 return rightness < 0; 2643 case View.FOCUS_RIGHT: 2644 return rightness > 0; 2645 case View.FOCUS_UP: 2646 return downness < 0; 2647 case View.FOCUS_DOWN: 2648 return downness > 0; 2649 case View.FOCUS_FORWARD: 2650 return downness > 0 || (downness == 0 && rightness * rtl >= 0); 2651 case View.FOCUS_BACKWARD: 2652 return downness < 0 || (downness == 0 && rightness * rtl <= 0); 2653 } 2654 throw new IllegalArgumentException("Invalid direction: " + direction + exceptionLabel()); 2655 } 2656 2657 @Override 2658 public void requestChildFocus(View child, View focused) { 2659 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { 2660 requestChildOnScreen(child, focused); 2661 } 2662 super.requestChildFocus(child, focused); 2663 } 2664 2665 /** 2666 * Requests that the given child of the RecyclerView be positioned onto the screen. This method 2667 * can be called for both unfocusable and focusable child views. For unfocusable child views, 2668 * the {@param focused} parameter passed is null, whereas for a focusable child, this parameter 2669 * indicates the actual descendant view within this child view that holds the focus. 2670 * @param child The child view of this RecyclerView that wants to come onto the screen. 2671 * @param focused The descendant view that actually has the focus if child is focusable, null 2672 * otherwise. 2673 */ 2674 private void requestChildOnScreen(@NonNull View child, @Nullable View focused) { 2675 View rectView = (focused != null) ? focused : child; 2676 mTempRect.set(0, 0, rectView.getWidth(), rectView.getHeight()); 2677 2678 // get item decor offsets w/o refreshing. If they are invalid, there will be another 2679 // layout pass to fix them, then it is LayoutManager's responsibility to keep focused 2680 // View in viewport. 2681 final ViewGroup.LayoutParams focusedLayoutParams = rectView.getLayoutParams(); 2682 if (focusedLayoutParams instanceof LayoutParams) { 2683 // if focused child has item decors, use them. Otherwise, ignore. 2684 final LayoutParams lp = (LayoutParams) focusedLayoutParams; 2685 if (!lp.mInsetsDirty) { 2686 final Rect insets = lp.mDecorInsets; 2687 mTempRect.left -= insets.left; 2688 mTempRect.right += insets.right; 2689 mTempRect.top -= insets.top; 2690 mTempRect.bottom += insets.bottom; 2691 } 2692 } 2693 2694 if (focused != null) { 2695 offsetDescendantRectToMyCoords(focused, mTempRect); 2696 offsetRectIntoDescendantCoords(child, mTempRect); 2697 } 2698 mLayout.requestChildRectangleOnScreen(this, child, mTempRect, !mFirstLayoutComplete, 2699 (focused == null)); 2700 } 2701 2702 @Override 2703 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 2704 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); 2705 } 2706 2707 @Override 2708 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 2709 if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { 2710 super.addFocusables(views, direction, focusableMode); 2711 } 2712 } 2713 2714 @Override 2715 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 2716 if (isComputingLayout()) { 2717 // if we are in the middle of a layout calculation, don't let any child take focus. 2718 // RV will handle it after layout calculation is finished. 2719 return false; 2720 } 2721 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 2722 } 2723 2724 @Override 2725 protected void onAttachedToWindow() { 2726 super.onAttachedToWindow(); 2727 mLayoutOrScrollCounter = 0; 2728 mIsAttached = true; 2729 mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); 2730 if (mLayout != null) { 2731 mLayout.dispatchAttachedToWindow(this); 2732 } 2733 mPostedAnimatorRunner = false; 2734 2735 if (ALLOW_THREAD_GAP_WORK) { 2736 // Register with gap worker 2737 mGapWorker = GapWorker.sGapWorker.get(); 2738 if (mGapWorker == null) { 2739 mGapWorker = new GapWorker(); 2740 2741 // break 60 fps assumption if data from display appears valid 2742 // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) 2743 Display display = ViewCompat.getDisplay(this); 2744 float refreshRate = 60.0f; 2745 if (!isInEditMode() && display != null) { 2746 float displayRefreshRate = display.getRefreshRate(); 2747 if (displayRefreshRate >= 30.0f) { 2748 refreshRate = displayRefreshRate; 2749 } 2750 } 2751 mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); 2752 GapWorker.sGapWorker.set(mGapWorker); 2753 } 2754 mGapWorker.add(this); 2755 } 2756 } 2757 2758 @Override 2759 protected void onDetachedFromWindow() { 2760 super.onDetachedFromWindow(); 2761 if (mItemAnimator != null) { 2762 mItemAnimator.endAnimations(); 2763 } 2764 stopScroll(); 2765 mIsAttached = false; 2766 if (mLayout != null) { 2767 mLayout.dispatchDetachedFromWindow(this, mRecycler); 2768 } 2769 mPendingAccessibilityImportanceChange.clear(); 2770 removeCallbacks(mItemAnimatorRunner); 2771 mViewInfoStore.onDetach(); 2772 2773 if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) { 2774 // Unregister with gap worker 2775 mGapWorker.remove(this); 2776 mGapWorker = null; 2777 } 2778 } 2779 2780 /** 2781 * Returns true if RecyclerView is attached to window. 2782 */ 2783 @Override 2784 public boolean isAttachedToWindow() { 2785 return mIsAttached; 2786 } 2787 2788 /** 2789 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 2790 * {@link IllegalStateException} if it <b>is not</b>. 2791 * 2792 * @param message The message for the exception. Can be null. 2793 * @see #assertNotInLayoutOrScroll(String) 2794 */ 2795 void assertInLayoutOrScroll(String message) { 2796 if (!isComputingLayout()) { 2797 if (message == null) { 2798 throw new IllegalStateException("Cannot call this method unless RecyclerView is " 2799 + "computing a layout or scrolling" + exceptionLabel()); 2800 } 2801 throw new IllegalStateException(message + exceptionLabel()); 2802 2803 } 2804 } 2805 2806 /** 2807 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 2808 * {@link IllegalStateException} if it <b>is</b>. 2809 * 2810 * @param message The message for the exception. Can be null. 2811 * @see #assertInLayoutOrScroll(String) 2812 */ 2813 void assertNotInLayoutOrScroll(String message) { 2814 if (isComputingLayout()) { 2815 if (message == null) { 2816 throw new IllegalStateException("Cannot call this method while RecyclerView is " 2817 + "computing a layout or scrolling" + exceptionLabel()); 2818 } 2819 throw new IllegalStateException(message); 2820 } 2821 if (mDispatchScrollCounter > 0) { 2822 Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might" 2823 + "be run during a measure & layout pass where you cannot change the" 2824 + "RecyclerView data. Any method call that might change the structure" 2825 + "of the RecyclerView or the adapter contents should be postponed to" 2826 + "the next frame.", 2827 new IllegalStateException("" + exceptionLabel())); 2828 } 2829 } 2830 2831 /** 2832 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 2833 * to child views or this view's standard scrolling behavior. 2834 * 2835 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 2836 * returns true from 2837 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 2838 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 2839 * for each incoming MotionEvent until the end of the gesture.</p> 2840 * 2841 * @param listener Listener to add 2842 * @see SimpleOnItemTouchListener 2843 */ 2844 public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) { 2845 mOnItemTouchListeners.add(listener); 2846 } 2847 2848 /** 2849 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 2850 * 2851 * @param listener Listener to remove 2852 */ 2853 public void removeOnItemTouchListener(@NonNull OnItemTouchListener listener) { 2854 mOnItemTouchListeners.remove(listener); 2855 if (mActiveOnItemTouchListener == listener) { 2856 mActiveOnItemTouchListener = null; 2857 } 2858 } 2859 2860 private boolean dispatchOnItemTouchIntercept(MotionEvent e) { 2861 final int action = e.getAction(); 2862 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { 2863 mActiveOnItemTouchListener = null; 2864 } 2865 2866 final int listenerCount = mOnItemTouchListeners.size(); 2867 for (int i = 0; i < listenerCount; i++) { 2868 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 2869 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 2870 mActiveOnItemTouchListener = listener; 2871 return true; 2872 } 2873 } 2874 return false; 2875 } 2876 2877 private boolean dispatchOnItemTouch(MotionEvent e) { 2878 final int action = e.getAction(); 2879 if (mActiveOnItemTouchListener != null) { 2880 if (action == MotionEvent.ACTION_DOWN) { 2881 // Stale state from a previous gesture, we're starting a new one. Clear it. 2882 mActiveOnItemTouchListener = null; 2883 } else { 2884 mActiveOnItemTouchListener.onTouchEvent(this, e); 2885 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 2886 // Clean up for the next gesture. 2887 mActiveOnItemTouchListener = null; 2888 } 2889 return true; 2890 } 2891 } 2892 2893 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept 2894 // as called from onInterceptTouchEvent; skip it. 2895 if (action != MotionEvent.ACTION_DOWN) { 2896 final int listenerCount = mOnItemTouchListeners.size(); 2897 for (int i = 0; i < listenerCount; i++) { 2898 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 2899 if (listener.onInterceptTouchEvent(this, e)) { 2900 mActiveOnItemTouchListener = listener; 2901 return true; 2902 } 2903 } 2904 } 2905 return false; 2906 } 2907 2908 @Override 2909 public boolean onInterceptTouchEvent(MotionEvent e) { 2910 if (mLayoutFrozen) { 2911 // When layout is frozen, RV does not intercept the motion event. 2912 // A child view e.g. a button may still get the click. 2913 return false; 2914 } 2915 if (dispatchOnItemTouchIntercept(e)) { 2916 cancelTouch(); 2917 return true; 2918 } 2919 2920 if (mLayout == null) { 2921 return false; 2922 } 2923 2924 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 2925 final boolean canScrollVertically = mLayout.canScrollVertically(); 2926 2927 if (mVelocityTracker == null) { 2928 mVelocityTracker = VelocityTracker.obtain(); 2929 } 2930 mVelocityTracker.addMovement(e); 2931 2932 final int action = e.getActionMasked(); 2933 final int actionIndex = e.getActionIndex(); 2934 2935 switch (action) { 2936 case MotionEvent.ACTION_DOWN: 2937 if (mIgnoreMotionEventTillDown) { 2938 mIgnoreMotionEventTillDown = false; 2939 } 2940 mScrollPointerId = e.getPointerId(0); 2941 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 2942 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 2943 2944 if (mScrollState == SCROLL_STATE_SETTLING) { 2945 getParent().requestDisallowInterceptTouchEvent(true); 2946 setScrollState(SCROLL_STATE_DRAGGING); 2947 } 2948 2949 // Clear the nested offsets 2950 mNestedOffsets[0] = mNestedOffsets[1] = 0; 2951 2952 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2953 if (canScrollHorizontally) { 2954 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2955 } 2956 if (canScrollVertically) { 2957 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2958 } 2959 startNestedScroll(nestedScrollAxis, TYPE_TOUCH); 2960 break; 2961 2962 case MotionEvent.ACTION_POINTER_DOWN: 2963 mScrollPointerId = e.getPointerId(actionIndex); 2964 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 2965 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 2966 break; 2967 2968 case MotionEvent.ACTION_MOVE: { 2969 final int index = e.findPointerIndex(mScrollPointerId); 2970 if (index < 0) { 2971 Log.e(TAG, "Error processing scroll; pointer index for id " 2972 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 2973 return false; 2974 } 2975 2976 final int x = (int) (e.getX(index) + 0.5f); 2977 final int y = (int) (e.getY(index) + 0.5f); 2978 if (mScrollState != SCROLL_STATE_DRAGGING) { 2979 final int dx = x - mInitialTouchX; 2980 final int dy = y - mInitialTouchY; 2981 boolean startScroll = false; 2982 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 2983 mLastTouchX = x; 2984 startScroll = true; 2985 } 2986 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 2987 mLastTouchY = y; 2988 startScroll = true; 2989 } 2990 if (startScroll) { 2991 setScrollState(SCROLL_STATE_DRAGGING); 2992 } 2993 } 2994 } break; 2995 2996 case MotionEvent.ACTION_POINTER_UP: { 2997 onPointerUp(e); 2998 } break; 2999 3000 case MotionEvent.ACTION_UP: { 3001 mVelocityTracker.clear(); 3002 stopNestedScroll(TYPE_TOUCH); 3003 } break; 3004 3005 case MotionEvent.ACTION_CANCEL: { 3006 cancelTouch(); 3007 } 3008 } 3009 return mScrollState == SCROLL_STATE_DRAGGING; 3010 } 3011 3012 @Override 3013 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 3014 final int listenerCount = mOnItemTouchListeners.size(); 3015 for (int i = 0; i < listenerCount; i++) { 3016 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 3017 listener.onRequestDisallowInterceptTouchEvent(disallowIntercept); 3018 } 3019 super.requestDisallowInterceptTouchEvent(disallowIntercept); 3020 } 3021 3022 @Override 3023 public boolean onTouchEvent(MotionEvent e) { 3024 if (mLayoutFrozen || mIgnoreMotionEventTillDown) { 3025 return false; 3026 } 3027 if (dispatchOnItemTouch(e)) { 3028 cancelTouch(); 3029 return true; 3030 } 3031 3032 if (mLayout == null) { 3033 return false; 3034 } 3035 3036 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 3037 final boolean canScrollVertically = mLayout.canScrollVertically(); 3038 3039 if (mVelocityTracker == null) { 3040 mVelocityTracker = VelocityTracker.obtain(); 3041 } 3042 boolean eventAddedToVelocityTracker = false; 3043 3044 final MotionEvent vtev = MotionEvent.obtain(e); 3045 final int action = e.getActionMasked(); 3046 final int actionIndex = e.getActionIndex(); 3047 3048 if (action == MotionEvent.ACTION_DOWN) { 3049 mNestedOffsets[0] = mNestedOffsets[1] = 0; 3050 } 3051 vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); 3052 3053 switch (action) { 3054 case MotionEvent.ACTION_DOWN: { 3055 mScrollPointerId = e.getPointerId(0); 3056 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 3057 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 3058 3059 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 3060 if (canScrollHorizontally) { 3061 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 3062 } 3063 if (canScrollVertically) { 3064 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 3065 } 3066 startNestedScroll(nestedScrollAxis, TYPE_TOUCH); 3067 } break; 3068 3069 case MotionEvent.ACTION_POINTER_DOWN: { 3070 mScrollPointerId = e.getPointerId(actionIndex); 3071 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 3072 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 3073 } break; 3074 3075 case MotionEvent.ACTION_MOVE: { 3076 final int index = e.findPointerIndex(mScrollPointerId); 3077 if (index < 0) { 3078 Log.e(TAG, "Error processing scroll; pointer index for id " 3079 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 3080 return false; 3081 } 3082 3083 final int x = (int) (e.getX(index) + 0.5f); 3084 final int y = (int) (e.getY(index) + 0.5f); 3085 int dx = mLastTouchX - x; 3086 int dy = mLastTouchY - y; 3087 3088 if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) { 3089 dx -= mScrollConsumed[0]; 3090 dy -= mScrollConsumed[1]; 3091 vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 3092 // Updated the nested offsets 3093 mNestedOffsets[0] += mScrollOffset[0]; 3094 mNestedOffsets[1] += mScrollOffset[1]; 3095 } 3096 3097 if (mScrollState != SCROLL_STATE_DRAGGING) { 3098 boolean startScroll = false; 3099 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 3100 if (dx > 0) { 3101 dx -= mTouchSlop; 3102 } else { 3103 dx += mTouchSlop; 3104 } 3105 startScroll = true; 3106 } 3107 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 3108 if (dy > 0) { 3109 dy -= mTouchSlop; 3110 } else { 3111 dy += mTouchSlop; 3112 } 3113 startScroll = true; 3114 } 3115 if (startScroll) { 3116 setScrollState(SCROLL_STATE_DRAGGING); 3117 } 3118 } 3119 3120 if (mScrollState == SCROLL_STATE_DRAGGING) { 3121 mLastTouchX = x - mScrollOffset[0]; 3122 mLastTouchY = y - mScrollOffset[1]; 3123 3124 if (scrollByInternal( 3125 canScrollHorizontally ? dx : 0, 3126 canScrollVertically ? dy : 0, 3127 vtev)) { 3128 getParent().requestDisallowInterceptTouchEvent(true); 3129 } 3130 if (mGapWorker != null && (dx != 0 || dy != 0)) { 3131 mGapWorker.postFromTraversal(this, dx, dy); 3132 } 3133 } 3134 } break; 3135 3136 case MotionEvent.ACTION_POINTER_UP: { 3137 onPointerUp(e); 3138 } break; 3139 3140 case MotionEvent.ACTION_UP: { 3141 mVelocityTracker.addMovement(vtev); 3142 eventAddedToVelocityTracker = true; 3143 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 3144 final float xvel = canScrollHorizontally 3145 ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; 3146 final float yvel = canScrollVertically 3147 ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; 3148 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 3149 setScrollState(SCROLL_STATE_IDLE); 3150 } 3151 resetTouch(); 3152 } break; 3153 3154 case MotionEvent.ACTION_CANCEL: { 3155 cancelTouch(); 3156 } break; 3157 } 3158 3159 if (!eventAddedToVelocityTracker) { 3160 mVelocityTracker.addMovement(vtev); 3161 } 3162 vtev.recycle(); 3163 3164 return true; 3165 } 3166 3167 private void resetTouch() { 3168 if (mVelocityTracker != null) { 3169 mVelocityTracker.clear(); 3170 } 3171 stopNestedScroll(TYPE_TOUCH); 3172 releaseGlows(); 3173 } 3174 3175 private void cancelTouch() { 3176 resetTouch(); 3177 setScrollState(SCROLL_STATE_IDLE); 3178 } 3179 3180 private void onPointerUp(MotionEvent e) { 3181 final int actionIndex = e.getActionIndex(); 3182 if (e.getPointerId(actionIndex) == mScrollPointerId) { 3183 // Pick a new pointer to pick up the slack. 3184 final int newIndex = actionIndex == 0 ? 1 : 0; 3185 mScrollPointerId = e.getPointerId(newIndex); 3186 mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); 3187 mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); 3188 } 3189 } 3190 3191 @Override 3192 public boolean onGenericMotionEvent(MotionEvent event) { 3193 if (mLayout == null) { 3194 return false; 3195 } 3196 if (mLayoutFrozen) { 3197 return false; 3198 } 3199 if (event.getAction() == MotionEventCompat.ACTION_SCROLL) { 3200 final float vScroll, hScroll; 3201 if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { 3202 if (mLayout.canScrollVertically()) { 3203 // Inverse the sign of the vertical scroll to align the scroll orientation 3204 // with AbsListView. 3205 vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 3206 } else { 3207 vScroll = 0f; 3208 } 3209 if (mLayout.canScrollHorizontally()) { 3210 hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 3211 } else { 3212 hScroll = 0f; 3213 } 3214 } else if ((event.getSource() & InputDeviceCompat.SOURCE_ROTARY_ENCODER) != 0) { 3215 final float axisScroll = event.getAxisValue(MotionEventCompat.AXIS_SCROLL); 3216 if (mLayout.canScrollVertically()) { 3217 // Invert the sign of the vertical scroll to align the scroll orientation 3218 // with AbsListView. 3219 vScroll = -axisScroll; 3220 hScroll = 0f; 3221 } else if (mLayout.canScrollHorizontally()) { 3222 vScroll = 0f; 3223 hScroll = axisScroll; 3224 } else { 3225 vScroll = 0f; 3226 hScroll = 0f; 3227 } 3228 } else { 3229 vScroll = 0f; 3230 hScroll = 0f; 3231 } 3232 3233 if (vScroll != 0 || hScroll != 0) { 3234 scrollByInternal((int) (hScroll * mScaledHorizontalScrollFactor), 3235 (int) (vScroll * mScaledVerticalScrollFactor), event); 3236 } 3237 } 3238 return false; 3239 } 3240 3241 @Override 3242 protected void onMeasure(int widthSpec, int heightSpec) { 3243 if (mLayout == null) { 3244 defaultOnMeasure(widthSpec, heightSpec); 3245 return; 3246 } 3247 if (mLayout.isAutoMeasureEnabled()) { 3248 final int widthMode = MeasureSpec.getMode(widthSpec); 3249 final int heightMode = MeasureSpec.getMode(heightSpec); 3250 3251 /** 3252 * This specific call should be considered deprecated and replaced with 3253 * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could 3254 * break existing third party code but all documentation directs developers to not 3255 * override {@link LayoutManager#onMeasure(int, int)} when 3256 * {@link LayoutManager#isAutoMeasureEnabled()} returns true. 3257 */ 3258 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3259 3260 final boolean measureSpecModeIsExactly = 3261 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; 3262 if (measureSpecModeIsExactly || mAdapter == null) { 3263 return; 3264 } 3265 3266 if (mState.mLayoutStep == State.STEP_START) { 3267 dispatchLayoutStep1(); 3268 } 3269 // set dimensions in 2nd step. Pre-layout should happen with old dimensions for 3270 // consistency 3271 mLayout.setMeasureSpecs(widthSpec, heightSpec); 3272 mState.mIsMeasuring = true; 3273 dispatchLayoutStep2(); 3274 3275 // now we can get the width and height from the children. 3276 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 3277 3278 // if RecyclerView has non-exact width and height and if there is at least one child 3279 // which also has non-exact width & height, we have to re-measure. 3280 if (mLayout.shouldMeasureTwice()) { 3281 mLayout.setMeasureSpecs( 3282 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 3283 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 3284 mState.mIsMeasuring = true; 3285 dispatchLayoutStep2(); 3286 // now we can get the width and height from the children. 3287 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 3288 } 3289 } else { 3290 if (mHasFixedSize) { 3291 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3292 return; 3293 } 3294 // custom onMeasure 3295 if (mAdapterUpdateDuringMeasure) { 3296 startInterceptRequestLayout(); 3297 onEnterLayoutOrScroll(); 3298 processAdapterUpdatesAndSetAnimationFlags(); 3299 onExitLayoutOrScroll(); 3300 3301 if (mState.mRunPredictiveAnimations) { 3302 mState.mInPreLayout = true; 3303 } else { 3304 // consume remaining updates to provide a consistent state with the layout pass. 3305 mAdapterHelper.consumeUpdatesInOnePass(); 3306 mState.mInPreLayout = false; 3307 } 3308 mAdapterUpdateDuringMeasure = false; 3309 stopInterceptRequestLayout(false); 3310 } else if (mState.mRunPredictiveAnimations) { 3311 // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: 3312 // this means there is already an onMeasure() call performed to handle the pending 3313 // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout 3314 // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time 3315 // because getViewForPosition() will crash when LM uses a child to measure. 3316 setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); 3317 return; 3318 } 3319 3320 if (mAdapter != null) { 3321 mState.mItemCount = mAdapter.getItemCount(); 3322 } else { 3323 mState.mItemCount = 0; 3324 } 3325 startInterceptRequestLayout(); 3326 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3327 stopInterceptRequestLayout(false); 3328 mState.mInPreLayout = false; // clear 3329 } 3330 } 3331 3332 /** 3333 * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios 3334 * where this RecyclerView is otherwise lacking better information. 3335 */ 3336 void defaultOnMeasure(int widthSpec, int heightSpec) { 3337 // calling LayoutManager here is not pretty but that API is already public and it is better 3338 // than creating another method since this is internal. 3339 final int width = LayoutManager.chooseSize(widthSpec, 3340 getPaddingLeft() + getPaddingRight(), 3341 ViewCompat.getMinimumWidth(this)); 3342 final int height = LayoutManager.chooseSize(heightSpec, 3343 getPaddingTop() + getPaddingBottom(), 3344 ViewCompat.getMinimumHeight(this)); 3345 3346 setMeasuredDimension(width, height); 3347 } 3348 3349 @Override 3350 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 3351 super.onSizeChanged(w, h, oldw, oldh); 3352 if (w != oldw || h != oldh) { 3353 invalidateGlows(); 3354 // layout's w/h are updated during measure/layout steps. 3355 } 3356 } 3357 3358 /** 3359 * Sets the {@link ItemAnimator} that will handle animations involving changes 3360 * to the items in this RecyclerView. By default, RecyclerView instantiates and 3361 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are 3362 * enabled for the RecyclerView depends on the ItemAnimator and whether 3363 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() 3364 * supports item animations}. 3365 * 3366 * @param animator The ItemAnimator being set. If null, no animations will occur 3367 * when changes occur to the items in this RecyclerView. 3368 */ 3369 public void setItemAnimator(@Nullable ItemAnimator animator) { 3370 if (mItemAnimator != null) { 3371 mItemAnimator.endAnimations(); 3372 mItemAnimator.setListener(null); 3373 } 3374 mItemAnimator = animator; 3375 if (mItemAnimator != null) { 3376 mItemAnimator.setListener(mItemAnimatorListener); 3377 } 3378 } 3379 3380 void onEnterLayoutOrScroll() { 3381 mLayoutOrScrollCounter++; 3382 } 3383 3384 void onExitLayoutOrScroll() { 3385 onExitLayoutOrScroll(true); 3386 } 3387 3388 void onExitLayoutOrScroll(boolean enableChangeEvents) { 3389 mLayoutOrScrollCounter--; 3390 if (mLayoutOrScrollCounter < 1) { 3391 if (DEBUG && mLayoutOrScrollCounter < 0) { 3392 throw new IllegalStateException("layout or scroll counter cannot go below zero." 3393 + "Some calls are not matching" + exceptionLabel()); 3394 } 3395 mLayoutOrScrollCounter = 0; 3396 if (enableChangeEvents) { 3397 dispatchContentChangedIfNecessary(); 3398 dispatchPendingImportantForAccessibilityChanges(); 3399 } 3400 } 3401 } 3402 3403 boolean isAccessibilityEnabled() { 3404 return mAccessibilityManager != null && mAccessibilityManager.isEnabled(); 3405 } 3406 3407 private void dispatchContentChangedIfNecessary() { 3408 final int flags = mEatenAccessibilityChangeFlags; 3409 mEatenAccessibilityChangeFlags = 0; 3410 if (flags != 0 && isAccessibilityEnabled()) { 3411 final AccessibilityEvent event = AccessibilityEvent.obtain(); 3412 event.setEventType(AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); 3413 AccessibilityEventCompat.setContentChangeTypes(event, flags); 3414 sendAccessibilityEventUnchecked(event); 3415 } 3416 } 3417 3418 /** 3419 * Returns whether RecyclerView is currently computing a layout. 3420 * <p> 3421 * If this method returns true, it means that RecyclerView is in a lockdown state and any 3422 * attempt to update adapter contents will result in an exception because adapter contents 3423 * cannot be changed while RecyclerView is trying to compute the layout. 3424 * <p> 3425 * It is very unlikely that your code will be running during this state as it is 3426 * called by the framework when a layout traversal happens or RecyclerView starts to scroll 3427 * in response to system events (touch, accessibility etc). 3428 * <p> 3429 * This case may happen if you have some custom logic to change adapter contents in 3430 * response to a View callback (e.g. focus change callback) which might be triggered during a 3431 * layout calculation. In these cases, you should just postpone the change using a Handler or a 3432 * similar mechanism. 3433 * 3434 * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code> 3435 * otherwise 3436 */ 3437 public boolean isComputingLayout() { 3438 return mLayoutOrScrollCounter > 0; 3439 } 3440 3441 /** 3442 * Returns true if an accessibility event should not be dispatched now. This happens when an 3443 * accessibility request arrives while RecyclerView does not have a stable state which is very 3444 * hard to handle for a LayoutManager. Instead, this method records necessary information about 3445 * the event and dispatches a window change event after the critical section is finished. 3446 * 3447 * @return True if the accessibility event should be postponed. 3448 */ 3449 boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) { 3450 if (isComputingLayout()) { 3451 int type = 0; 3452 if (event != null) { 3453 type = AccessibilityEventCompat.getContentChangeTypes(event); 3454 } 3455 if (type == 0) { 3456 type = AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED; 3457 } 3458 mEatenAccessibilityChangeFlags |= type; 3459 return true; 3460 } 3461 return false; 3462 } 3463 3464 @Override 3465 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 3466 if (shouldDeferAccessibilityEvent(event)) { 3467 return; 3468 } 3469 super.sendAccessibilityEventUnchecked(event); 3470 } 3471 3472 /** 3473 * Gets the current ItemAnimator for this RecyclerView. A null return value 3474 * indicates that there is no animator and that item changes will happen without 3475 * any animations. By default, RecyclerView instantiates and 3476 * uses an instance of {@link DefaultItemAnimator}. 3477 * 3478 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur 3479 * when changes occur to the items in this RecyclerView. 3480 */ 3481 @Nullable 3482 public ItemAnimator getItemAnimator() { 3483 return mItemAnimator; 3484 } 3485 3486 /** 3487 * Post a runnable to the next frame to run pending item animations. Only the first such 3488 * request will be posted, governed by the mPostedAnimatorRunner flag. 3489 */ 3490 void postAnimationRunner() { 3491 if (!mPostedAnimatorRunner && mIsAttached) { 3492 ViewCompat.postOnAnimation(this, mItemAnimatorRunner); 3493 mPostedAnimatorRunner = true; 3494 } 3495 } 3496 3497 private boolean predictiveItemAnimationsEnabled() { 3498 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); 3499 } 3500 3501 /** 3502 * Consumes adapter updates and calculates which type of animations we want to run. 3503 * Called in onMeasure and dispatchLayout. 3504 * <p> 3505 * This method may process only the pre-layout state of updates or all of them. 3506 */ 3507 private void processAdapterUpdatesAndSetAnimationFlags() { 3508 if (mDataSetHasChangedAfterLayout) { 3509 // Processing these items have no value since data set changed unexpectedly. 3510 // Instead, we just reset it. 3511 mAdapterHelper.reset(); 3512 if (mDispatchItemsChangedEvent) { 3513 mLayout.onItemsChanged(this); 3514 } 3515 } 3516 // simple animations are a subset of advanced animations (which will cause a 3517 // pre-layout step) 3518 // If layout supports predictive animations, pre-process to decide if we want to run them 3519 if (predictiveItemAnimationsEnabled()) { 3520 mAdapterHelper.preProcess(); 3521 } else { 3522 mAdapterHelper.consumeUpdatesInOnePass(); 3523 } 3524 boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; 3525 mState.mRunSimpleAnimations = mFirstLayoutComplete 3526 && mItemAnimator != null 3527 && (mDataSetHasChangedAfterLayout 3528 || animationTypeSupported 3529 || mLayout.mRequestedSimpleAnimations) 3530 && (!mDataSetHasChangedAfterLayout 3531 || mAdapter.hasStableIds()); 3532 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations 3533 && animationTypeSupported 3534 && !mDataSetHasChangedAfterLayout 3535 && predictiveItemAnimationsEnabled(); 3536 } 3537 3538 /** 3539 * Wrapper around layoutChildren() that handles animating changes caused by layout. 3540 * Animations work on the assumption that there are five different kinds of items 3541 * in play: 3542 * PERSISTENT: items are visible before and after layout 3543 * REMOVED: items were visible before layout and were removed by the app 3544 * ADDED: items did not exist before layout and were added by the app 3545 * DISAPPEARING: items exist in the data set before/after, but changed from 3546 * visible to non-visible in the process of layout (they were moved off 3547 * screen as a side-effect of other changes) 3548 * APPEARING: items exist in the data set before/after, but changed from 3549 * non-visible to visible in the process of layout (they were moved on 3550 * screen as a side-effect of other changes) 3551 * The overall approach figures out what items exist before/after layout and 3552 * infers one of the five above states for each of the items. Then the animations 3553 * are set up accordingly: 3554 * PERSISTENT views are animated via 3555 * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3556 * DISAPPEARING views are animated via 3557 * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3558 * APPEARING views are animated via 3559 * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3560 * and changed views are animated via 3561 * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. 3562 */ 3563 void dispatchLayout() { 3564 if (mAdapter == null) { 3565 Log.e(TAG, "No adapter attached; skipping layout"); 3566 // leave the state in START 3567 return; 3568 } 3569 if (mLayout == null) { 3570 Log.e(TAG, "No layout manager attached; skipping layout"); 3571 // leave the state in START 3572 return; 3573 } 3574 mState.mIsMeasuring = false; 3575 if (mState.mLayoutStep == State.STEP_START) { 3576 dispatchLayoutStep1(); 3577 mLayout.setExactMeasureSpecsFrom(this); 3578 dispatchLayoutStep2(); 3579 } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() 3580 || mLayout.getHeight() != getHeight()) { 3581 // First 2 steps are done in onMeasure but looks like we have to run again due to 3582 // changed size. 3583 mLayout.setExactMeasureSpecsFrom(this); 3584 dispatchLayoutStep2(); 3585 } else { 3586 // always make sure we sync them (to ensure mode is exact) 3587 mLayout.setExactMeasureSpecsFrom(this); 3588 } 3589 dispatchLayoutStep3(); 3590 } 3591 3592 private void saveFocusInfo() { 3593 View child = null; 3594 if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { 3595 child = getFocusedChild(); 3596 } 3597 3598 final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); 3599 if (focusedVh == null) { 3600 resetFocusInfo(); 3601 } else { 3602 mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; 3603 // mFocusedItemPosition should hold the current adapter position of the previously 3604 // focused item. If the item is removed, we store the previous adapter position of the 3605 // removed item. 3606 mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION 3607 : (focusedVh.isRemoved() ? focusedVh.mOldPosition 3608 : focusedVh.getAdapterPosition()); 3609 mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); 3610 } 3611 } 3612 3613 private void resetFocusInfo() { 3614 mState.mFocusedItemId = NO_ID; 3615 mState.mFocusedItemPosition = NO_POSITION; 3616 mState.mFocusedSubChildId = View.NO_ID; 3617 } 3618 3619 /** 3620 * Finds the best view candidate to request focus on using mFocusedItemPosition index of the 3621 * previously focused item. It first traverses the adapter forward to find a focusable candidate 3622 * and if no such candidate is found, it reverses the focus search direction for the items 3623 * before the mFocusedItemPosition'th index; 3624 * @return The best candidate to request focus on, or null if no such candidate exists. Null 3625 * indicates all the existing adapter items are unfocusable. 3626 */ 3627 @Nullable 3628 private View findNextViewToFocus() { 3629 int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition 3630 : 0; 3631 ViewHolder nextFocus; 3632 final int itemCount = mState.getItemCount(); 3633 for (int i = startFocusSearchIndex; i < itemCount; i++) { 3634 nextFocus = findViewHolderForAdapterPosition(i); 3635 if (nextFocus == null) { 3636 break; 3637 } 3638 if (nextFocus.itemView.hasFocusable()) { 3639 return nextFocus.itemView; 3640 } 3641 } 3642 final int limit = Math.min(itemCount, startFocusSearchIndex); 3643 for (int i = limit - 1; i >= 0; i--) { 3644 nextFocus = findViewHolderForAdapterPosition(i); 3645 if (nextFocus == null) { 3646 return null; 3647 } 3648 if (nextFocus.itemView.hasFocusable()) { 3649 return nextFocus.itemView; 3650 } 3651 } 3652 return null; 3653 } 3654 3655 private void recoverFocusFromState() { 3656 if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() 3657 || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS 3658 || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { 3659 // No-op if either of these cases happens: 3660 // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus 3661 // before its children and is focused (i.e. it already stole the focus away from its 3662 // descendants). 3663 return; 3664 } 3665 // only recover focus if RV itself has the focus or the focused view is hidden 3666 if (!isFocused()) { 3667 final View focusedChild = getFocusedChild(); 3668 if (IGNORE_DETACHED_FOCUSED_CHILD 3669 && (focusedChild.getParent() == null || !focusedChild.hasFocus())) { 3670 // Special handling of API 15-. A focused child can be invalid because mFocus is not 3671 // cleared when the child is detached (mParent = null), 3672 // This happens because clearFocus on API 15- does not invalidate mFocus of its 3673 // parent when this child is detached. 3674 // For API 16+, this is not an issue because requestFocus takes care of clearing the 3675 // prior detached focused child. For API 15- the problem happens in 2 cases because 3676 // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called 3677 // for the current focused item which calls clearChild or 2. when the prior focused 3678 // child is removed, removeDetachedView called in layout step 3 which calls 3679 // clearChild. We should ignore this invalid focused child in all our calculations 3680 // for the next view to receive focus, and apply the focus recovery logic instead. 3681 if (mChildHelper.getChildCount() == 0) { 3682 // No children left. Request focus on the RV itself since one of its children 3683 // was holding focus previously. 3684 requestFocus(); 3685 return; 3686 } 3687 } else if (!mChildHelper.isHidden(focusedChild)) { 3688 // If the currently focused child is hidden, apply the focus recovery logic. 3689 // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. 3690 return; 3691 } 3692 } 3693 ViewHolder focusTarget = null; 3694 // RV first attempts to locate the previously focused item to request focus on using 3695 // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to 3696 // find the next best candidate to request focus on based on mFocusedItemPosition. 3697 if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { 3698 focusTarget = findViewHolderForItemId(mState.mFocusedItemId); 3699 } 3700 View viewToFocus = null; 3701 if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) 3702 || !focusTarget.itemView.hasFocusable()) { 3703 if (mChildHelper.getChildCount() > 0) { 3704 // At this point, RV has focus and either of these conditions are true: 3705 // 1. There's no previously focused item either because RV received focused before 3706 // layout, or the previously focused item was removed, or RV doesn't have stable IDs 3707 // 2. Previous focus child is hidden, or 3. Previous focused child is no longer 3708 // focusable. In either of these cases, we make sure that RV still passes down the 3709 // focus to one of its focusable children using a best-effort algorithm. 3710 viewToFocus = findNextViewToFocus(); 3711 } 3712 } else { 3713 // looks like the focused item has been replaced with another view that represents the 3714 // same item in the adapter. Request focus on that. 3715 viewToFocus = focusTarget.itemView; 3716 } 3717 3718 if (viewToFocus != null) { 3719 if (mState.mFocusedSubChildId != NO_ID) { 3720 View child = viewToFocus.findViewById(mState.mFocusedSubChildId); 3721 if (child != null && child.isFocusable()) { 3722 viewToFocus = child; 3723 } 3724 } 3725 viewToFocus.requestFocus(); 3726 } 3727 } 3728 3729 private int getDeepestFocusedViewWithId(View view) { 3730 int lastKnownId = view.getId(); 3731 while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { 3732 view = ((ViewGroup) view).getFocusedChild(); 3733 final int id = view.getId(); 3734 if (id != View.NO_ID) { 3735 lastKnownId = view.getId(); 3736 } 3737 } 3738 return lastKnownId; 3739 } 3740 3741 final void fillRemainingScrollValues(State state) { 3742 if (getScrollState() == SCROLL_STATE_SETTLING) { 3743 final OverScroller scroller = mViewFlinger.mScroller; 3744 state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX(); 3745 state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY(); 3746 } else { 3747 state.mRemainingScrollHorizontal = 0; 3748 state.mRemainingScrollVertical = 0; 3749 } 3750 } 3751 3752 /** 3753 * The first step of a layout where we; 3754 * - process adapter updates 3755 * - decide which animation should run 3756 * - save information about current views 3757 * - If necessary, run predictive layout and save its information 3758 */ 3759 private void dispatchLayoutStep1() { 3760 mState.assertLayoutStep(State.STEP_START); 3761 fillRemainingScrollValues(mState); 3762 mState.mIsMeasuring = false; 3763 startInterceptRequestLayout(); 3764 mViewInfoStore.clear(); 3765 onEnterLayoutOrScroll(); 3766 processAdapterUpdatesAndSetAnimationFlags(); 3767 saveFocusInfo(); 3768 mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; 3769 mItemsAddedOrRemoved = mItemsChanged = false; 3770 mState.mInPreLayout = mState.mRunPredictiveAnimations; 3771 mState.mItemCount = mAdapter.getItemCount(); 3772 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 3773 3774 if (mState.mRunSimpleAnimations) { 3775 // Step 0: Find out where all non-removed items are, pre-layout 3776 int count = mChildHelper.getChildCount(); 3777 for (int i = 0; i < count; ++i) { 3778 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 3779 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { 3780 continue; 3781 } 3782 final ItemHolderInfo animationInfo = mItemAnimator 3783 .recordPreLayoutInformation(mState, holder, 3784 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), 3785 holder.getUnmodifiedPayloads()); 3786 mViewInfoStore.addToPreLayout(holder, animationInfo); 3787 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() 3788 && !holder.shouldIgnore() && !holder.isInvalid()) { 3789 long key = getChangedHolderKey(holder); 3790 // This is NOT the only place where a ViewHolder is added to old change holders 3791 // list. There is another case where: 3792 // * A VH is currently hidden but not deleted 3793 // * The hidden item is changed in the adapter 3794 // * Layout manager decides to layout the item in the pre-Layout pass (step1) 3795 // When this case is detected, RV will un-hide that view and add to the old 3796 // change holders list. 3797 mViewInfoStore.addToOldChangeHolders(key, holder); 3798 } 3799 } 3800 } 3801 if (mState.mRunPredictiveAnimations) { 3802 // Step 1: run prelayout: This will use the old positions of items. The layout manager 3803 // is expected to layout everything, even removed items (though not to add removed 3804 // items back to the container). This gives the pre-layout position of APPEARING views 3805 // which come into existence as part of the real layout. 3806 3807 // Save old positions so that LayoutManager can run its mapping logic. 3808 saveOldPositions(); 3809 final boolean didStructureChange = mState.mStructureChanged; 3810 mState.mStructureChanged = false; 3811 // temporarily disable flag because we are asking for previous layout 3812 mLayout.onLayoutChildren(mRecycler, mState); 3813 mState.mStructureChanged = didStructureChange; 3814 3815 for (int i = 0; i < mChildHelper.getChildCount(); ++i) { 3816 final View child = mChildHelper.getChildAt(i); 3817 final ViewHolder viewHolder = getChildViewHolderInt(child); 3818 if (viewHolder.shouldIgnore()) { 3819 continue; 3820 } 3821 if (!mViewInfoStore.isInPreLayout(viewHolder)) { 3822 int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); 3823 boolean wasHidden = viewHolder 3824 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 3825 if (!wasHidden) { 3826 flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 3827 } 3828 final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( 3829 mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); 3830 if (wasHidden) { 3831 recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); 3832 } else { 3833 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 3834 } 3835 } 3836 } 3837 // we don't process disappearing list because they may re-appear in post layout pass. 3838 clearOldPositions(); 3839 } else { 3840 clearOldPositions(); 3841 } 3842 onExitLayoutOrScroll(); 3843 stopInterceptRequestLayout(false); 3844 mState.mLayoutStep = State.STEP_LAYOUT; 3845 } 3846 3847 /** 3848 * The second layout step where we do the actual layout of the views for the final state. 3849 * This step might be run multiple times if necessary (e.g. measure). 3850 */ 3851 private void dispatchLayoutStep2() { 3852 startInterceptRequestLayout(); 3853 onEnterLayoutOrScroll(); 3854 mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); 3855 mAdapterHelper.consumeUpdatesInOnePass(); 3856 mState.mItemCount = mAdapter.getItemCount(); 3857 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; 3858 3859 // Step 2: Run layout 3860 mState.mInPreLayout = false; 3861 mLayout.onLayoutChildren(mRecycler, mState); 3862 3863 mState.mStructureChanged = false; 3864 mPendingSavedState = null; 3865 3866 // onLayoutChildren may have caused client code to disable item animations; re-check 3867 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; 3868 mState.mLayoutStep = State.STEP_ANIMATIONS; 3869 onExitLayoutOrScroll(); 3870 stopInterceptRequestLayout(false); 3871 } 3872 3873 /** 3874 * The final step of the layout where we save the information about views for animations, 3875 * trigger animations and do any necessary cleanup. 3876 */ 3877 private void dispatchLayoutStep3() { 3878 mState.assertLayoutStep(State.STEP_ANIMATIONS); 3879 startInterceptRequestLayout(); 3880 onEnterLayoutOrScroll(); 3881 mState.mLayoutStep = State.STEP_START; 3882 if (mState.mRunSimpleAnimations) { 3883 // Step 3: Find out where things are now, and process change animations. 3884 // traverse list in reverse because we may call animateChange in the loop which may 3885 // remove the target view holder. 3886 for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { 3887 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 3888 if (holder.shouldIgnore()) { 3889 continue; 3890 } 3891 long key = getChangedHolderKey(holder); 3892 final ItemHolderInfo animationInfo = mItemAnimator 3893 .recordPostLayoutInformation(mState, holder); 3894 ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); 3895 if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { 3896 // run a change animation 3897 3898 // If an Item is CHANGED but the updated version is disappearing, it creates 3899 // a conflicting case. 3900 // Since a view that is marked as disappearing is likely to be going out of 3901 // bounds, we run a change animation. Both views will be cleaned automatically 3902 // once their animations finish. 3903 // On the other hand, if it is the same view holder instance, we run a 3904 // disappearing animation instead because we are not going to rebind the updated 3905 // VH unless it is enforced by the layout manager. 3906 final boolean oldDisappearing = mViewInfoStore.isDisappearing( 3907 oldChangeViewHolder); 3908 final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); 3909 if (oldDisappearing && oldChangeViewHolder == holder) { 3910 // run disappear animation instead of change 3911 mViewInfoStore.addToPostLayout(holder, animationInfo); 3912 } else { 3913 final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( 3914 oldChangeViewHolder); 3915 // we add and remove so that any post info is merged. 3916 mViewInfoStore.addToPostLayout(holder, animationInfo); 3917 ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); 3918 if (preInfo == null) { 3919 handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); 3920 } else { 3921 animateChange(oldChangeViewHolder, holder, preInfo, postInfo, 3922 oldDisappearing, newDisappearing); 3923 } 3924 } 3925 } else { 3926 mViewInfoStore.addToPostLayout(holder, animationInfo); 3927 } 3928 } 3929 3930 // Step 4: Process view info lists and trigger animations 3931 mViewInfoStore.process(mViewInfoProcessCallback); 3932 } 3933 3934 mLayout.removeAndRecycleScrapInt(mRecycler); 3935 mState.mPreviousLayoutItemCount = mState.mItemCount; 3936 mDataSetHasChangedAfterLayout = false; 3937 mDispatchItemsChangedEvent = false; 3938 mState.mRunSimpleAnimations = false; 3939 3940 mState.mRunPredictiveAnimations = false; 3941 mLayout.mRequestedSimpleAnimations = false; 3942 if (mRecycler.mChangedScrap != null) { 3943 mRecycler.mChangedScrap.clear(); 3944 } 3945 if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { 3946 // Initial prefetch has expanded cache, so reset until next prefetch. 3947 // This prevents initial prefetches from expanding the cache permanently. 3948 mLayout.mPrefetchMaxCountObserved = 0; 3949 mLayout.mPrefetchMaxObservedInInitialPrefetch = false; 3950 mRecycler.updateViewCacheSize(); 3951 } 3952 3953 mLayout.onLayoutCompleted(mState); 3954 onExitLayoutOrScroll(); 3955 stopInterceptRequestLayout(false); 3956 mViewInfoStore.clear(); 3957 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { 3958 dispatchOnScrolled(0, 0); 3959 } 3960 recoverFocusFromState(); 3961 resetFocusInfo(); 3962 } 3963 3964 /** 3965 * This handles the case where there is an unexpected VH missing in the pre-layout map. 3966 * <p> 3967 * We might be able to detect the error in the application which will help the developer to 3968 * resolve the issue. 3969 * <p> 3970 * If it is not an expected error, we at least print an error to notify the developer and ignore 3971 * the animation. 3972 * 3973 * https://code.google.com/p/android/issues/detail?id=193958 3974 * 3975 * @param key The change key 3976 * @param holder Current ViewHolder 3977 * @param oldChangeViewHolder Changed ViewHolder 3978 */ 3979 private void handleMissingPreInfoForChangeError(long key, 3980 ViewHolder holder, ViewHolder oldChangeViewHolder) { 3981 // check if two VH have the same key, if so, print that as an error 3982 final int childCount = mChildHelper.getChildCount(); 3983 for (int i = 0; i < childCount; i++) { 3984 View view = mChildHelper.getChildAt(i); 3985 ViewHolder other = getChildViewHolderInt(view); 3986 if (other == holder) { 3987 continue; 3988 } 3989 final long otherKey = getChangedHolderKey(other); 3990 if (otherKey == key) { 3991 if (mAdapter != null && mAdapter.hasStableIds()) { 3992 throw new IllegalStateException("Two different ViewHolders have the same stable" 3993 + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" 3994 + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 3995 + exceptionLabel()); 3996 } else { 3997 throw new IllegalStateException("Two different ViewHolders have the same change" 3998 + " ID. This might happen due to inconsistent Adapter update events or" 3999 + " if the LayoutManager lays out the same View multiple times." 4000 + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 4001 + exceptionLabel()); 4002 } 4003 } 4004 } 4005 // Very unlikely to happen but if it does, notify the developer. 4006 Log.e(TAG, "Problem while matching changed view holders with the new" 4007 + "ones. The pre-layout information for the change holder " + oldChangeViewHolder 4008 + " cannot be found but it is necessary for " + holder + exceptionLabel()); 4009 } 4010 4011 /** 4012 * Records the animation information for a view holder that was bounced from hidden list. It 4013 * also clears the bounce back flag. 4014 */ 4015 void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, 4016 ItemHolderInfo animationInfo) { 4017 // looks like this view bounced back from hidden list! 4018 viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 4019 if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() 4020 && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { 4021 long key = getChangedHolderKey(viewHolder); 4022 mViewInfoStore.addToOldChangeHolders(key, viewHolder); 4023 } 4024 mViewInfoStore.addToPreLayout(viewHolder, animationInfo); 4025 } 4026 4027 private void findMinMaxChildLayoutPositions(int[] into) { 4028 final int count = mChildHelper.getChildCount(); 4029 if (count == 0) { 4030 into[0] = NO_POSITION; 4031 into[1] = NO_POSITION; 4032 return; 4033 } 4034 int minPositionPreLayout = Integer.MAX_VALUE; 4035 int maxPositionPreLayout = Integer.MIN_VALUE; 4036 for (int i = 0; i < count; ++i) { 4037 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 4038 if (holder.shouldIgnore()) { 4039 continue; 4040 } 4041 final int pos = holder.getLayoutPosition(); 4042 if (pos < minPositionPreLayout) { 4043 minPositionPreLayout = pos; 4044 } 4045 if (pos > maxPositionPreLayout) { 4046 maxPositionPreLayout = pos; 4047 } 4048 } 4049 into[0] = minPositionPreLayout; 4050 into[1] = maxPositionPreLayout; 4051 } 4052 4053 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { 4054 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 4055 return mMinMaxLayoutPositions[0] != minPositionPreLayout 4056 || mMinMaxLayoutPositions[1] != maxPositionPreLayout; 4057 } 4058 4059 @Override 4060 protected void removeDetachedView(View child, boolean animate) { 4061 ViewHolder vh = getChildViewHolderInt(child); 4062 if (vh != null) { 4063 if (vh.isTmpDetached()) { 4064 vh.clearTmpDetachFlag(); 4065 } else if (!vh.shouldIgnore()) { 4066 throw new IllegalArgumentException("Called removeDetachedView with a view which" 4067 + " is not flagged as tmp detached." + vh + exceptionLabel()); 4068 } 4069 } 4070 4071 // Clear any android.view.animation.Animation that may prevent the item from 4072 // detaching when being removed. If a child is re-added before the 4073 // lazy detach occurs, it will receive invalid attach/detach sequencing. 4074 child.clearAnimation(); 4075 4076 dispatchChildDetached(child); 4077 super.removeDetachedView(child, animate); 4078 } 4079 4080 /** 4081 * Returns a unique key to be used while handling change animations. 4082 * It might be child's position or stable id depending on the adapter type. 4083 */ 4084 long getChangedHolderKey(ViewHolder holder) { 4085 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; 4086 } 4087 4088 void animateAppearance(@NonNull ViewHolder itemHolder, 4089 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { 4090 itemHolder.setIsRecyclable(false); 4091 if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { 4092 postAnimationRunner(); 4093 } 4094 } 4095 4096 void animateDisappearance(@NonNull ViewHolder holder, 4097 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { 4098 addAnimatingView(holder); 4099 holder.setIsRecyclable(false); 4100 if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { 4101 postAnimationRunner(); 4102 } 4103 } 4104 4105 private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, 4106 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, 4107 boolean oldHolderDisappearing, boolean newHolderDisappearing) { 4108 oldHolder.setIsRecyclable(false); 4109 if (oldHolderDisappearing) { 4110 addAnimatingView(oldHolder); 4111 } 4112 if (oldHolder != newHolder) { 4113 if (newHolderDisappearing) { 4114 addAnimatingView(newHolder); 4115 } 4116 oldHolder.mShadowedHolder = newHolder; 4117 // old holder should disappear after animation ends 4118 addAnimatingView(oldHolder); 4119 mRecycler.unscrapView(oldHolder); 4120 newHolder.setIsRecyclable(false); 4121 newHolder.mShadowingHolder = oldHolder; 4122 } 4123 if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { 4124 postAnimationRunner(); 4125 } 4126 } 4127 4128 @Override 4129 protected void onLayout(boolean changed, int l, int t, int r, int b) { 4130 TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); 4131 dispatchLayout(); 4132 TraceCompat.endSection(); 4133 mFirstLayoutComplete = true; 4134 } 4135 4136 @Override 4137 public void requestLayout() { 4138 if (mInterceptRequestLayoutDepth == 0 && !mLayoutFrozen) { 4139 super.requestLayout(); 4140 } else { 4141 mLayoutWasDefered = true; 4142 } 4143 } 4144 4145 void markItemDecorInsetsDirty() { 4146 final int childCount = mChildHelper.getUnfilteredChildCount(); 4147 for (int i = 0; i < childCount; i++) { 4148 final View child = mChildHelper.getUnfilteredChildAt(i); 4149 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 4150 } 4151 mRecycler.markItemDecorInsetsDirty(); 4152 } 4153 4154 @Override 4155 public void draw(Canvas c) { 4156 super.draw(c); 4157 4158 final int count = mItemDecorations.size(); 4159 for (int i = 0; i < count; i++) { 4160 mItemDecorations.get(i).onDrawOver(c, this, mState); 4161 } 4162 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we 4163 // need find children closest to edges. Not sure if it is worth the effort. 4164 boolean needsInvalidate = false; 4165 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 4166 final int restore = c.save(); 4167 final int padding = mClipToPadding ? getPaddingBottom() : 0; 4168 c.rotate(270); 4169 c.translate(-getHeight() + padding, 0); 4170 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 4171 c.restoreToCount(restore); 4172 } 4173 if (mTopGlow != null && !mTopGlow.isFinished()) { 4174 final int restore = c.save(); 4175 if (mClipToPadding) { 4176 c.translate(getPaddingLeft(), getPaddingTop()); 4177 } 4178 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 4179 c.restoreToCount(restore); 4180 } 4181 if (mRightGlow != null && !mRightGlow.isFinished()) { 4182 final int restore = c.save(); 4183 final int width = getWidth(); 4184 final int padding = mClipToPadding ? getPaddingTop() : 0; 4185 c.rotate(90); 4186 c.translate(-padding, -width); 4187 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 4188 c.restoreToCount(restore); 4189 } 4190 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 4191 final int restore = c.save(); 4192 c.rotate(180); 4193 if (mClipToPadding) { 4194 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); 4195 } else { 4196 c.translate(-getWidth(), -getHeight()); 4197 } 4198 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 4199 c.restoreToCount(restore); 4200 } 4201 4202 // If some views are animating, ItemDecorators are likely to move/change with them. 4203 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's 4204 // display lists are not invalidated. 4205 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 4206 && mItemAnimator.isRunning()) { 4207 needsInvalidate = true; 4208 } 4209 4210 if (needsInvalidate) { 4211 ViewCompat.postInvalidateOnAnimation(this); 4212 } 4213 } 4214 4215 @Override 4216 public void onDraw(Canvas c) { 4217 super.onDraw(c); 4218 4219 final int count = mItemDecorations.size(); 4220 for (int i = 0; i < count; i++) { 4221 mItemDecorations.get(i).onDraw(c, this, mState); 4222 } 4223 } 4224 4225 @Override 4226 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 4227 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 4228 } 4229 4230 @Override 4231 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 4232 if (mLayout == null) { 4233 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4234 } 4235 return mLayout.generateDefaultLayoutParams(); 4236 } 4237 4238 @Override 4239 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 4240 if (mLayout == null) { 4241 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4242 } 4243 return mLayout.generateLayoutParams(getContext(), attrs); 4244 } 4245 4246 @Override 4247 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 4248 if (mLayout == null) { 4249 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4250 } 4251 return mLayout.generateLayoutParams(p); 4252 } 4253 4254 /** 4255 * Returns true if RecyclerView is currently running some animations. 4256 * <p> 4257 * If you want to be notified when animations are finished, use 4258 * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}. 4259 * 4260 * @return True if there are some item animations currently running or waiting to be started. 4261 */ 4262 public boolean isAnimating() { 4263 return mItemAnimator != null && mItemAnimator.isRunning(); 4264 } 4265 4266 void saveOldPositions() { 4267 final int childCount = mChildHelper.getUnfilteredChildCount(); 4268 for (int i = 0; i < childCount; i++) { 4269 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4270 if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { 4271 throw new IllegalStateException("view holder cannot have position -1 unless it" 4272 + " is removed" + exceptionLabel()); 4273 } 4274 if (!holder.shouldIgnore()) { 4275 holder.saveOldPosition(); 4276 } 4277 } 4278 } 4279 4280 void clearOldPositions() { 4281 final int childCount = mChildHelper.getUnfilteredChildCount(); 4282 for (int i = 0; i < childCount; i++) { 4283 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4284 if (!holder.shouldIgnore()) { 4285 holder.clearOldPosition(); 4286 } 4287 } 4288 mRecycler.clearOldPositions(); 4289 } 4290 4291 void offsetPositionRecordsForMove(int from, int to) { 4292 final int childCount = mChildHelper.getUnfilteredChildCount(); 4293 final int start, end, inBetweenOffset; 4294 if (from < to) { 4295 start = from; 4296 end = to; 4297 inBetweenOffset = -1; 4298 } else { 4299 start = to; 4300 end = from; 4301 inBetweenOffset = 1; 4302 } 4303 4304 for (int i = 0; i < childCount; i++) { 4305 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4306 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 4307 continue; 4308 } 4309 if (DEBUG) { 4310 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " 4311 + holder); 4312 } 4313 if (holder.mPosition == from) { 4314 holder.offsetPosition(to - from, false); 4315 } else { 4316 holder.offsetPosition(inBetweenOffset, false); 4317 } 4318 4319 mState.mStructureChanged = true; 4320 } 4321 mRecycler.offsetPositionRecordsForMove(from, to); 4322 requestLayout(); 4323 } 4324 4325 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 4326 final int childCount = mChildHelper.getUnfilteredChildCount(); 4327 for (int i = 0; i < childCount; i++) { 4328 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4329 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { 4330 if (DEBUG) { 4331 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " 4332 + holder + " now at position " + (holder.mPosition + itemCount)); 4333 } 4334 holder.offsetPosition(itemCount, false); 4335 mState.mStructureChanged = true; 4336 } 4337 } 4338 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 4339 requestLayout(); 4340 } 4341 4342 void offsetPositionRecordsForRemove(int positionStart, int itemCount, 4343 boolean applyToPreLayout) { 4344 final int positionEnd = positionStart + itemCount; 4345 final int childCount = mChildHelper.getUnfilteredChildCount(); 4346 for (int i = 0; i < childCount; i++) { 4347 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4348 if (holder != null && !holder.shouldIgnore()) { 4349 if (holder.mPosition >= positionEnd) { 4350 if (DEBUG) { 4351 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 4352 + " holder " + holder + " now at position " 4353 + (holder.mPosition - itemCount)); 4354 } 4355 holder.offsetPosition(-itemCount, applyToPreLayout); 4356 mState.mStructureChanged = true; 4357 } else if (holder.mPosition >= positionStart) { 4358 if (DEBUG) { 4359 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 4360 + " holder " + holder + " now REMOVED"); 4361 } 4362 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, 4363 applyToPreLayout); 4364 mState.mStructureChanged = true; 4365 } 4366 } 4367 } 4368 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); 4369 requestLayout(); 4370 } 4371 4372 /** 4373 * Rebind existing views for the given range, or create as needed. 4374 * 4375 * @param positionStart Adapter position to start at 4376 * @param itemCount Number of views that must explicitly be rebound 4377 */ 4378 void viewRangeUpdate(int positionStart, int itemCount, Object payload) { 4379 final int childCount = mChildHelper.getUnfilteredChildCount(); 4380 final int positionEnd = positionStart + itemCount; 4381 4382 for (int i = 0; i < childCount; i++) { 4383 final View child = mChildHelper.getUnfilteredChildAt(i); 4384 final ViewHolder holder = getChildViewHolderInt(child); 4385 if (holder == null || holder.shouldIgnore()) { 4386 continue; 4387 } 4388 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 4389 // We re-bind these view holders after pre-processing is complete so that 4390 // ViewHolders have their final positions assigned. 4391 holder.addFlags(ViewHolder.FLAG_UPDATE); 4392 holder.addChangePayload(payload); 4393 // lp cannot be null since we get ViewHolder from it. 4394 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 4395 } 4396 } 4397 mRecycler.viewRangeUpdate(positionStart, itemCount); 4398 } 4399 4400 boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { 4401 return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, 4402 viewHolder.getUnmodifiedPayloads()); 4403 } 4404 4405 /** 4406 * Processes the fact that, as far as we can tell, the data set has completely changed. 4407 * 4408 * <ul> 4409 * <li>Once layout occurs, all attached items should be discarded or animated. 4410 * <li>Attached items are labeled as invalid. 4411 * <li>Because items may still be prefetched between a "data set completely changed" 4412 * event and a layout event, all cached items are discarded. 4413 * </ul> 4414 * 4415 * @param dispatchItemsChanged Whether to call 4416 * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout. 4417 */ 4418 void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { 4419 mDispatchItemsChangedEvent |= dispatchItemsChanged; 4420 mDataSetHasChangedAfterLayout = true; 4421 markKnownViewsInvalid(); 4422 } 4423 4424 /** 4425 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 4426 * data change event. 4427 */ 4428 void markKnownViewsInvalid() { 4429 final int childCount = mChildHelper.getUnfilteredChildCount(); 4430 for (int i = 0; i < childCount; i++) { 4431 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4432 if (holder != null && !holder.shouldIgnore()) { 4433 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 4434 } 4435 } 4436 markItemDecorInsetsDirty(); 4437 mRecycler.markKnownViewsInvalid(); 4438 } 4439 4440 /** 4441 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method 4442 * will trigger a {@link #requestLayout()} call. 4443 */ 4444 public void invalidateItemDecorations() { 4445 if (mItemDecorations.size() == 0) { 4446 return; 4447 } 4448 if (mLayout != null) { 4449 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" 4450 + " or layout"); 4451 } 4452 markItemDecorInsetsDirty(); 4453 requestLayout(); 4454 } 4455 4456 /** 4457 * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's 4458 * focus even if the View representing the Item is replaced during a layout calculation. 4459 * <p> 4460 * By default, this value is {@code true}. 4461 * 4462 * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses 4463 * focus. 4464 * 4465 * @see #setPreserveFocusAfterLayout(boolean) 4466 */ 4467 public boolean getPreserveFocusAfterLayout() { 4468 return mPreserveFocusAfterLayout; 4469 } 4470 4471 /** 4472 * Set whether the RecyclerView should try to keep the same Item focused after a layout 4473 * calculation or not. 4474 * <p> 4475 * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, 4476 * views may lose focus during a layout calculation as their state changes or they are replaced 4477 * with another view due to type change or animation. In these cases, RecyclerView can request 4478 * focus on the new view automatically. 4479 * 4480 * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a 4481 * layout calculations. Defaults to true. 4482 * 4483 * @see #getPreserveFocusAfterLayout() 4484 */ 4485 public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { 4486 mPreserveFocusAfterLayout = preserveFocusAfterLayout; 4487 } 4488 4489 /** 4490 * Retrieve the {@link ViewHolder} for the given child view. 4491 * 4492 * @param child Child of this RecyclerView to query for its ViewHolder 4493 * @return The child view's ViewHolder 4494 */ 4495 public ViewHolder getChildViewHolder(View child) { 4496 final ViewParent parent = child.getParent(); 4497 if (parent != null && parent != this) { 4498 throw new IllegalArgumentException("View " + child + " is not a direct child of " 4499 + this); 4500 } 4501 return getChildViewHolderInt(child); 4502 } 4503 4504 /** 4505 * Traverses the ancestors of the given view and returns the item view that contains it and 4506 * also a direct child of the RecyclerView. This returned view can be used to get the 4507 * ViewHolder by calling {@link #getChildViewHolder(View)}. 4508 * 4509 * @param view The view that is a descendant of the RecyclerView. 4510 * 4511 * @return The direct child of the RecyclerView which contains the given view or null if the 4512 * provided view is not a descendant of this RecyclerView. 4513 * 4514 * @see #getChildViewHolder(View) 4515 * @see #findContainingViewHolder(View) 4516 */ 4517 @Nullable 4518 public View findContainingItemView(View view) { 4519 ViewParent parent = view.getParent(); 4520 while (parent != null && parent != this && parent instanceof View) { 4521 view = (View) parent; 4522 parent = view.getParent(); 4523 } 4524 return parent == this ? view : null; 4525 } 4526 4527 /** 4528 * Returns the ViewHolder that contains the given view. 4529 * 4530 * @param view The view that is a descendant of the RecyclerView. 4531 * 4532 * @return The ViewHolder that contains the given view or null if the provided view is not a 4533 * descendant of this RecyclerView. 4534 */ 4535 @Nullable 4536 public ViewHolder findContainingViewHolder(View view) { 4537 View itemView = findContainingItemView(view); 4538 return itemView == null ? null : getChildViewHolder(itemView); 4539 } 4540 4541 4542 static ViewHolder getChildViewHolderInt(View child) { 4543 if (child == null) { 4544 return null; 4545 } 4546 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 4547 } 4548 4549 /** 4550 * @deprecated use {@link #getChildAdapterPosition(View)} or 4551 * {@link #getChildLayoutPosition(View)}. 4552 */ 4553 @Deprecated 4554 public int getChildPosition(View child) { 4555 return getChildAdapterPosition(child); 4556 } 4557 4558 /** 4559 * Return the adapter position that the given child view corresponds to. 4560 * 4561 * @param child Child View to query 4562 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 4563 */ 4564 public int getChildAdapterPosition(@NonNull View child) { 4565 final ViewHolder holder = getChildViewHolderInt(child); 4566 return holder != null ? holder.getAdapterPosition() : NO_POSITION; 4567 } 4568 4569 /** 4570 * Return the adapter position of the given child view as of the latest completed layout pass. 4571 * <p> 4572 * This position may not be equal to Item's adapter position if there are pending changes 4573 * in the adapter which have not been reflected to the layout yet. 4574 * 4575 * @param child Child View to query 4576 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if 4577 * the View is representing a removed item. 4578 */ 4579 public int getChildLayoutPosition(@NonNull View child) { 4580 final ViewHolder holder = getChildViewHolderInt(child); 4581 return holder != null ? holder.getLayoutPosition() : NO_POSITION; 4582 } 4583 4584 /** 4585 * Return the stable item id that the given child view corresponds to. 4586 * 4587 * @param child Child View to query 4588 * @return Item id corresponding to the given view or {@link #NO_ID} 4589 */ 4590 public long getChildItemId(@NonNull View child) { 4591 if (mAdapter == null || !mAdapter.hasStableIds()) { 4592 return NO_ID; 4593 } 4594 final ViewHolder holder = getChildViewHolderInt(child); 4595 return holder != null ? holder.getItemId() : NO_ID; 4596 } 4597 4598 /** 4599 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or 4600 * {@link #findViewHolderForAdapterPosition(int)} 4601 */ 4602 @Deprecated 4603 public ViewHolder findViewHolderForPosition(int position) { 4604 return findViewHolderForPosition(position, false); 4605 } 4606 4607 /** 4608 * Return the ViewHolder for the item in the given position of the data set as of the latest 4609 * layout pass. 4610 * <p> 4611 * This method checks only the children of RecyclerView. If the item at the given 4612 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 4613 * <p> 4614 * Note that when Adapter contents change, ViewHolder positions are not updated until the 4615 * next layout calculation. If there are pending adapter updates, the return value of this 4616 * method may not match your adapter contents. You can use 4617 * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. 4618 * <p> 4619 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 4620 * with the same layout position representing the same Item. In this case, the updated 4621 * ViewHolder will be returned. 4622 * 4623 * @param position The position of the item in the data set of the adapter 4624 * @return The ViewHolder at <code>position</code> or null if there is no such item 4625 */ 4626 public ViewHolder findViewHolderForLayoutPosition(int position) { 4627 return findViewHolderForPosition(position, false); 4628 } 4629 4630 /** 4631 * Return the ViewHolder for the item in the given position of the data set. Unlike 4632 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending 4633 * adapter changes that may not be reflected to the layout yet. On the other hand, if 4634 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been 4635 * calculated yet, this method will return <code>null</code> since the new positions of views 4636 * are unknown until the layout is calculated. 4637 * <p> 4638 * This method checks only the children of RecyclerView. If the item at the given 4639 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 4640 * <p> 4641 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 4642 * representing the same Item. In this case, the updated ViewHolder will be returned. 4643 * 4644 * @param position The position of the item in the data set of the adapter 4645 * @return The ViewHolder at <code>position</code> or null if there is no such item 4646 */ 4647 public ViewHolder findViewHolderForAdapterPosition(int position) { 4648 if (mDataSetHasChangedAfterLayout) { 4649 return null; 4650 } 4651 final int childCount = mChildHelper.getUnfilteredChildCount(); 4652 // hidden VHs are not preferred but if that is the only one we find, we rather return it 4653 ViewHolder hidden = null; 4654 for (int i = 0; i < childCount; i++) { 4655 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4656 if (holder != null && !holder.isRemoved() 4657 && getAdapterPositionFor(holder) == position) { 4658 if (mChildHelper.isHidden(holder.itemView)) { 4659 hidden = holder; 4660 } else { 4661 return holder; 4662 } 4663 } 4664 } 4665 return hidden; 4666 } 4667 4668 ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { 4669 final int childCount = mChildHelper.getUnfilteredChildCount(); 4670 ViewHolder hidden = null; 4671 for (int i = 0; i < childCount; i++) { 4672 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4673 if (holder != null && !holder.isRemoved()) { 4674 if (checkNewPosition) { 4675 if (holder.mPosition != position) { 4676 continue; 4677 } 4678 } else if (holder.getLayoutPosition() != position) { 4679 continue; 4680 } 4681 if (mChildHelper.isHidden(holder.itemView)) { 4682 hidden = holder; 4683 } else { 4684 return holder; 4685 } 4686 } 4687 } 4688 // This method should not query cached views. It creates a problem during adapter updates 4689 // when we are dealing with already laid out views. Also, for the public method, it is more 4690 // reasonable to return null if position is not laid out. 4691 return hidden; 4692 } 4693 4694 /** 4695 * Return the ViewHolder for the item with the given id. The RecyclerView must 4696 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 4697 * return a non-null value. 4698 * <p> 4699 * This method checks only the children of RecyclerView. If the item with the given 4700 * <code>id</code> is not laid out, it <em>will not</em> create a new one. 4701 * 4702 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the 4703 * same id. In this case, the updated ViewHolder will be returned. 4704 * 4705 * @param id The id for the requested item 4706 * @return The ViewHolder with the given <code>id</code> or null if there is no such item 4707 */ 4708 public ViewHolder findViewHolderForItemId(long id) { 4709 if (mAdapter == null || !mAdapter.hasStableIds()) { 4710 return null; 4711 } 4712 final int childCount = mChildHelper.getUnfilteredChildCount(); 4713 ViewHolder hidden = null; 4714 for (int i = 0; i < childCount; i++) { 4715 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4716 if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { 4717 if (mChildHelper.isHidden(holder.itemView)) { 4718 hidden = holder; 4719 } else { 4720 return holder; 4721 } 4722 } 4723 } 4724 return hidden; 4725 } 4726 4727 /** 4728 * Find the topmost view under the given point. 4729 * 4730 * @param x Horizontal position in pixels to search 4731 * @param y Vertical position in pixels to search 4732 * @return The child view under (x, y) or null if no matching child is found 4733 */ 4734 @Nullable 4735 public View findChildViewUnder(float x, float y) { 4736 final int count = mChildHelper.getChildCount(); 4737 for (int i = count - 1; i >= 0; i--) { 4738 final View child = mChildHelper.getChildAt(i); 4739 final float translationX = child.getTranslationX(); 4740 final float translationY = child.getTranslationY(); 4741 if (x >= child.getLeft() + translationX 4742 && x <= child.getRight() + translationX 4743 && y >= child.getTop() + translationY 4744 && y <= child.getBottom() + translationY) { 4745 return child; 4746 } 4747 } 4748 return null; 4749 } 4750 4751 @Override 4752 public boolean drawChild(Canvas canvas, View child, long drawingTime) { 4753 return super.drawChild(canvas, child, drawingTime); 4754 } 4755 4756 /** 4757 * Offset the bounds of all child views by <code>dy</code> pixels. 4758 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 4759 * 4760 * @param dy Vertical pixel offset to apply to the bounds of all child views 4761 */ 4762 public void offsetChildrenVertical(@Px int dy) { 4763 final int childCount = mChildHelper.getChildCount(); 4764 for (int i = 0; i < childCount; i++) { 4765 mChildHelper.getChildAt(i).offsetTopAndBottom(dy); 4766 } 4767 } 4768 4769 /** 4770 * Called when an item view is attached to this RecyclerView. 4771 * 4772 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 4773 * of child views as they become attached. This will be called before a 4774 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 4775 * changes.</p> 4776 * 4777 * @param child Child view that is now attached to this RecyclerView and its associated window 4778 */ 4779 public void onChildAttachedToWindow(@NonNull View child) { 4780 } 4781 4782 /** 4783 * Called when an item view is detached from this RecyclerView. 4784 * 4785 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 4786 * of child views as they become detached. This will be called as a 4787 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 4788 * 4789 * @param child Child view that is now detached from this RecyclerView and its associated window 4790 */ 4791 public void onChildDetachedFromWindow(@NonNull View child) { 4792 } 4793 4794 /** 4795 * Offset the bounds of all child views by <code>dx</code> pixels. 4796 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 4797 * 4798 * @param dx Horizontal pixel offset to apply to the bounds of all child views 4799 */ 4800 public void offsetChildrenHorizontal(@Px int dx) { 4801 final int childCount = mChildHelper.getChildCount(); 4802 for (int i = 0; i < childCount; i++) { 4803 mChildHelper.getChildAt(i).offsetLeftAndRight(dx); 4804 } 4805 } 4806 4807 /** 4808 * Returns the bounds of the view including its decoration and margins. 4809 * 4810 * @param view The view element to check 4811 * @param outBounds A rect that will receive the bounds of the element including its 4812 * decoration and margins. 4813 */ 4814 public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { 4815 getDecoratedBoundsWithMarginsInt(view, outBounds); 4816 } 4817 4818 static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { 4819 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 4820 final Rect insets = lp.mDecorInsets; 4821 outBounds.set(view.getLeft() - insets.left - lp.leftMargin, 4822 view.getTop() - insets.top - lp.topMargin, 4823 view.getRight() + insets.right + lp.rightMargin, 4824 view.getBottom() + insets.bottom + lp.bottomMargin); 4825 } 4826 4827 Rect getItemDecorInsetsForChild(View child) { 4828 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 4829 if (!lp.mInsetsDirty) { 4830 return lp.mDecorInsets; 4831 } 4832 4833 if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { 4834 // changed/invalid items should not be updated until they are rebound. 4835 return lp.mDecorInsets; 4836 } 4837 final Rect insets = lp.mDecorInsets; 4838 insets.set(0, 0, 0, 0); 4839 final int decorCount = mItemDecorations.size(); 4840 for (int i = 0; i < decorCount; i++) { 4841 mTempRect.set(0, 0, 0, 0); 4842 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 4843 insets.left += mTempRect.left; 4844 insets.top += mTempRect.top; 4845 insets.right += mTempRect.right; 4846 insets.bottom += mTempRect.bottom; 4847 } 4848 lp.mInsetsDirty = false; 4849 return insets; 4850 } 4851 4852 /** 4853 * Called when the scroll position of this RecyclerView changes. Subclasses should use 4854 * this method to respond to scrolling within the adapter's data set instead of an explicit 4855 * listener. 4856 * 4857 * <p>This method will always be invoked before listeners. If a subclass needs to perform 4858 * any additional upkeep or bookkeeping after scrolling but before listeners run, 4859 * this is a good place to do so.</p> 4860 * 4861 * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives 4862 * the distance scrolled in either direction within the adapter's data set instead of absolute 4863 * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from 4864 * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive 4865 * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which 4866 * do not correspond to the data set scroll position. However, some subclasses may choose 4867 * to use these fields as special offsets.</p> 4868 * 4869 * @param dx horizontal distance scrolled in pixels 4870 * @param dy vertical distance scrolled in pixels 4871 */ 4872 public void onScrolled(@Px int dx, @Px int dy) { 4873 // Do nothing 4874 } 4875 4876 void dispatchOnScrolled(int hresult, int vresult) { 4877 mDispatchScrollCounter++; 4878 // Pass the current scrollX/scrollY values; no actual change in these properties occurred 4879 // but some general-purpose code may choose to respond to changes this way. 4880 final int scrollX = getScrollX(); 4881 final int scrollY = getScrollY(); 4882 onScrollChanged(scrollX, scrollY, scrollX, scrollY); 4883 4884 // Pass the real deltas to onScrolled, the RecyclerView-specific method. 4885 onScrolled(hresult, vresult); 4886 4887 // Invoke listeners last. Subclassed view methods always handle the event first. 4888 // All internal state is consistent by the time listeners are invoked. 4889 if (mScrollListener != null) { 4890 mScrollListener.onScrolled(this, hresult, vresult); 4891 } 4892 if (mScrollListeners != null) { 4893 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 4894 mScrollListeners.get(i).onScrolled(this, hresult, vresult); 4895 } 4896 } 4897 mDispatchScrollCounter--; 4898 } 4899 4900 /** 4901 * Called when the scroll state of this RecyclerView changes. Subclasses should use this 4902 * method to respond to state changes instead of an explicit listener. 4903 * 4904 * <p>This method will always be invoked before listeners, but after the LayoutManager 4905 * responds to the scroll state change.</p> 4906 * 4907 * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, 4908 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} 4909 */ 4910 public void onScrollStateChanged(int state) { 4911 // Do nothing 4912 } 4913 4914 void dispatchOnScrollStateChanged(int state) { 4915 // Let the LayoutManager go first; this allows it to bring any properties into 4916 // a consistent state before the RecyclerView subclass responds. 4917 if (mLayout != null) { 4918 mLayout.onScrollStateChanged(state); 4919 } 4920 4921 // Let the RecyclerView subclass handle this event next; any LayoutManager property 4922 // changes will be reflected by this time. 4923 onScrollStateChanged(state); 4924 4925 // Listeners go last. All other internal state is consistent by this point. 4926 if (mScrollListener != null) { 4927 mScrollListener.onScrollStateChanged(this, state); 4928 } 4929 if (mScrollListeners != null) { 4930 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 4931 mScrollListeners.get(i).onScrollStateChanged(this, state); 4932 } 4933 } 4934 } 4935 4936 /** 4937 * Returns whether there are pending adapter updates which are not yet applied to the layout. 4938 * <p> 4939 * If this method returns <code>true</code>, it means that what user is currently seeing may not 4940 * reflect them adapter contents (depending on what has changed). 4941 * You may use this information to defer or cancel some operations. 4942 * <p> 4943 * This method returns true if RecyclerView has not yet calculated the first layout after it is 4944 * attached to the Window or the Adapter has been replaced. 4945 * 4946 * @return True if there are some adapter updates which are not yet reflected to layout or false 4947 * if layout is up to date. 4948 */ 4949 public boolean hasPendingAdapterUpdates() { 4950 return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout 4951 || mAdapterHelper.hasPendingUpdates(); 4952 } 4953 4954 class ViewFlinger implements Runnable { 4955 private int mLastFlingX; 4956 private int mLastFlingY; 4957 private OverScroller mScroller; 4958 Interpolator mInterpolator = sQuinticInterpolator; 4959 4960 // When set to true, postOnAnimation callbacks are delayed until the run method completes 4961 private boolean mEatRunOnAnimationRequest = false; 4962 4963 // Tracks if postAnimationCallback should be re-attached when it is done 4964 private boolean mReSchedulePostAnimationCallback = false; 4965 4966 ViewFlinger() { 4967 mScroller = new OverScroller(getContext(), sQuinticInterpolator); 4968 } 4969 4970 @Override 4971 public void run() { 4972 if (mLayout == null) { 4973 stop(); 4974 return; // no layout, cannot scroll. 4975 } 4976 disableRunOnAnimationRequests(); 4977 consumePendingUpdateOperations(); 4978 // keep a local reference so that if it is changed during onAnimation method, it won't 4979 // cause unexpected behaviors 4980 final OverScroller scroller = mScroller; 4981 final SmoothScroller smoothScroller = mLayout.mSmoothScroller; 4982 if (scroller.computeScrollOffset()) { 4983 final int[] scrollConsumed = mScrollConsumed; 4984 final int x = scroller.getCurrX(); 4985 final int y = scroller.getCurrY(); 4986 int dx = x - mLastFlingX; 4987 int dy = y - mLastFlingY; 4988 int hresult = 0; 4989 int vresult = 0; 4990 mLastFlingX = x; 4991 mLastFlingY = y; 4992 int overscrollX = 0, overscrollY = 0; 4993 4994 if (dispatchNestedPreScroll(dx, dy, scrollConsumed, null, TYPE_NON_TOUCH)) { 4995 dx -= scrollConsumed[0]; 4996 dy -= scrollConsumed[1]; 4997 } 4998 4999 if (mAdapter != null) { 5000 scrollStep(dx, dy, mScrollStepConsumed); 5001 hresult = mScrollStepConsumed[0]; 5002 vresult = mScrollStepConsumed[1]; 5003 overscrollX = dx - hresult; 5004 overscrollY = dy - vresult; 5005 5006 if (smoothScroller != null && !smoothScroller.isPendingInitialRun() 5007 && smoothScroller.isRunning()) { 5008 final int adapterSize = mState.getItemCount(); 5009 if (adapterSize == 0) { 5010 smoothScroller.stop(); 5011 } else if (smoothScroller.getTargetPosition() >= adapterSize) { 5012 smoothScroller.setTargetPosition(adapterSize - 1); 5013 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 5014 } else { 5015 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 5016 } 5017 } 5018 } 5019 if (!mItemDecorations.isEmpty()) { 5020 invalidate(); 5021 } 5022 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 5023 considerReleasingGlowsOnScroll(dx, dy); 5024 } 5025 5026 if (!dispatchNestedScroll(hresult, vresult, overscrollX, overscrollY, null, 5027 TYPE_NON_TOUCH) 5028 && (overscrollX != 0 || overscrollY != 0)) { 5029 final int vel = (int) scroller.getCurrVelocity(); 5030 5031 int velX = 0; 5032 if (overscrollX != x) { 5033 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; 5034 } 5035 5036 int velY = 0; 5037 if (overscrollY != y) { 5038 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; 5039 } 5040 5041 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 5042 absorbGlows(velX, velY); 5043 } 5044 if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) 5045 && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { 5046 scroller.abortAnimation(); 5047 } 5048 } 5049 if (hresult != 0 || vresult != 0) { 5050 dispatchOnScrolled(hresult, vresult); 5051 } 5052 5053 if (!awakenScrollBars()) { 5054 invalidate(); 5055 } 5056 5057 final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically() 5058 && vresult == dy; 5059 final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally() 5060 && hresult == dx; 5061 final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal 5062 || fullyConsumedVertical; 5063 5064 if (scroller.isFinished() || (!fullyConsumedAny 5065 && !hasNestedScrollingParent(TYPE_NON_TOUCH))) { 5066 // setting state to idle will stop this. 5067 setScrollState(SCROLL_STATE_IDLE); 5068 if (ALLOW_THREAD_GAP_WORK) { 5069 mPrefetchRegistry.clearPrefetchPositions(); 5070 } 5071 stopNestedScroll(TYPE_NON_TOUCH); 5072 } else { 5073 postOnAnimation(); 5074 if (mGapWorker != null) { 5075 mGapWorker.postFromTraversal(RecyclerView.this, dx, dy); 5076 } 5077 } 5078 } 5079 // call this after the onAnimation is complete not to have inconsistent callbacks etc. 5080 if (smoothScroller != null) { 5081 if (smoothScroller.isPendingInitialRun()) { 5082 smoothScroller.onAnimation(0, 0); 5083 } 5084 if (!mReSchedulePostAnimationCallback) { 5085 smoothScroller.stop(); //stop if it does not trigger any scroll 5086 } 5087 } 5088 enableRunOnAnimationRequests(); 5089 } 5090 5091 private void disableRunOnAnimationRequests() { 5092 mReSchedulePostAnimationCallback = false; 5093 mEatRunOnAnimationRequest = true; 5094 } 5095 5096 private void enableRunOnAnimationRequests() { 5097 mEatRunOnAnimationRequest = false; 5098 if (mReSchedulePostAnimationCallback) { 5099 postOnAnimation(); 5100 } 5101 } 5102 5103 void postOnAnimation() { 5104 if (mEatRunOnAnimationRequest) { 5105 mReSchedulePostAnimationCallback = true; 5106 } else { 5107 removeCallbacks(this); 5108 ViewCompat.postOnAnimation(RecyclerView.this, this); 5109 } 5110 } 5111 5112 public void fling(int velocityX, int velocityY) { 5113 setScrollState(SCROLL_STATE_SETTLING); 5114 mLastFlingX = mLastFlingY = 0; 5115 mScroller.fling(0, 0, velocityX, velocityY, 5116 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 5117 postOnAnimation(); 5118 } 5119 5120 public void smoothScrollBy(int dx, int dy) { 5121 smoothScrollBy(dx, dy, 0, 0); 5122 } 5123 5124 public void smoothScrollBy(int dx, int dy, int vx, int vy) { 5125 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); 5126 } 5127 5128 private float distanceInfluenceForSnapDuration(float f) { 5129 f -= 0.5f; // center the values about 0. 5130 f *= 0.3f * (float) Math.PI / 2.0f; 5131 return (float) Math.sin(f); 5132 } 5133 5134 private int computeScrollDuration(int dx, int dy, int vx, int vy) { 5135 final int absDx = Math.abs(dx); 5136 final int absDy = Math.abs(dy); 5137 final boolean horizontal = absDx > absDy; 5138 final int velocity = (int) Math.sqrt(vx * vx + vy * vy); 5139 final int delta = (int) Math.sqrt(dx * dx + dy * dy); 5140 final int containerSize = horizontal ? getWidth() : getHeight(); 5141 final int halfContainerSize = containerSize / 2; 5142 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); 5143 final float distance = halfContainerSize + halfContainerSize 5144 * distanceInfluenceForSnapDuration(distanceRatio); 5145 5146 final int duration; 5147 if (velocity > 0) { 5148 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 5149 } else { 5150 float absDelta = (float) (horizontal ? absDx : absDy); 5151 duration = (int) (((absDelta / containerSize) + 1) * 300); 5152 } 5153 return Math.min(duration, MAX_SCROLL_DURATION); 5154 } 5155 5156 public void smoothScrollBy(int dx, int dy, int duration) { 5157 smoothScrollBy(dx, dy, duration, sQuinticInterpolator); 5158 } 5159 5160 public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { 5161 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0), 5162 interpolator == null ? sQuinticInterpolator : interpolator); 5163 } 5164 5165 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { 5166 if (mInterpolator != interpolator) { 5167 mInterpolator = interpolator; 5168 mScroller = new OverScroller(getContext(), interpolator); 5169 } 5170 setScrollState(SCROLL_STATE_SETTLING); 5171 mLastFlingX = mLastFlingY = 0; 5172 mScroller.startScroll(0, 0, dx, dy, duration); 5173 if (Build.VERSION.SDK_INT < 23) { 5174 // b/64931938 before API 23, startScroll() does not reset getCurX()/getCurY() 5175 // to start values, which causes fillRemainingScrollValues() put in obsolete values 5176 // for LayoutManager.onLayoutChildren(). 5177 mScroller.computeScrollOffset(); 5178 } 5179 postOnAnimation(); 5180 } 5181 5182 public void stop() { 5183 removeCallbacks(this); 5184 mScroller.abortAnimation(); 5185 } 5186 5187 } 5188 5189 void repositionShadowingViews() { 5190 // Fix up shadow views used by change animations 5191 int count = mChildHelper.getChildCount(); 5192 for (int i = 0; i < count; i++) { 5193 View view = mChildHelper.getChildAt(i); 5194 ViewHolder holder = getChildViewHolder(view); 5195 if (holder != null && holder.mShadowingHolder != null) { 5196 View shadowingView = holder.mShadowingHolder.itemView; 5197 int left = view.getLeft(); 5198 int top = view.getTop(); 5199 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { 5200 shadowingView.layout(left, top, 5201 left + shadowingView.getWidth(), 5202 top + shadowingView.getHeight()); 5203 } 5204 } 5205 } 5206 } 5207 5208 private class RecyclerViewDataObserver extends AdapterDataObserver { 5209 RecyclerViewDataObserver() { 5210 } 5211 5212 @Override 5213 public void onChanged() { 5214 assertNotInLayoutOrScroll(null); 5215 mState.mStructureChanged = true; 5216 5217 processDataSetCompletelyChanged(true); 5218 if (!mAdapterHelper.hasPendingUpdates()) { 5219 requestLayout(); 5220 } 5221 } 5222 5223 @Override 5224 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 5225 assertNotInLayoutOrScroll(null); 5226 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { 5227 triggerUpdateProcessor(); 5228 } 5229 } 5230 5231 @Override 5232 public void onItemRangeInserted(int positionStart, int itemCount) { 5233 assertNotInLayoutOrScroll(null); 5234 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 5235 triggerUpdateProcessor(); 5236 } 5237 } 5238 5239 @Override 5240 public void onItemRangeRemoved(int positionStart, int itemCount) { 5241 assertNotInLayoutOrScroll(null); 5242 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 5243 triggerUpdateProcessor(); 5244 } 5245 } 5246 5247 @Override 5248 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 5249 assertNotInLayoutOrScroll(null); 5250 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 5251 triggerUpdateProcessor(); 5252 } 5253 } 5254 5255 void triggerUpdateProcessor() { 5256 if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { 5257 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); 5258 } else { 5259 mAdapterUpdateDuringMeasure = true; 5260 requestLayout(); 5261 } 5262 } 5263 } 5264 5265 /** 5266 * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews. 5267 * 5268 * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory) 5269 */ 5270 public static class EdgeEffectFactory { 5271 5272 @Retention(RetentionPolicy.SOURCE) 5273 @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM}) 5274 public @interface EdgeDirection {} 5275 5276 /** 5277 * Direction constant for the left edge 5278 */ 5279 public static final int DIRECTION_LEFT = 0; 5280 5281 /** 5282 * Direction constant for the top edge 5283 */ 5284 public static final int DIRECTION_TOP = 1; 5285 5286 /** 5287 * Direction constant for the right edge 5288 */ 5289 public static final int DIRECTION_RIGHT = 2; 5290 5291 /** 5292 * Direction constant for the bottom edge 5293 */ 5294 public static final int DIRECTION_BOTTOM = 3; 5295 5296 /** 5297 * Create a new EdgeEffect for the provided direction. 5298 */ 5299 protected @NonNull EdgeEffect createEdgeEffect(@NonNull RecyclerView view, 5300 @EdgeDirection int direction) { 5301 return new EdgeEffect(view.getContext()); 5302 } 5303 } 5304 5305 /** 5306 * RecycledViewPool lets you share Views between multiple RecyclerViews. 5307 * <p> 5308 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool 5309 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. 5310 * <p> 5311 * RecyclerView automatically creates a pool for itself if you don't provide one. 5312 */ 5313 public static class RecycledViewPool { 5314 private static final int DEFAULT_MAX_SCRAP = 5; 5315 5316 /** 5317 * Tracks both pooled holders, as well as create/bind timing metadata for the given type. 5318 * 5319 * Note that this tracks running averages of create/bind time across all RecyclerViews 5320 * (and, indirectly, Adapters) that use this pool. 5321 * 5322 * 1) This enables us to track average create and bind times across multiple adapters. Even 5323 * though create (and especially bind) may behave differently for different Adapter 5324 * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. 5325 * 5326 * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return 5327 * false for all other views of its type for the same deadline. This prevents items 5328 * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. 5329 */ 5330 static class ScrapData { 5331 final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); 5332 int mMaxScrap = DEFAULT_MAX_SCRAP; 5333 long mCreateRunningAverageNs = 0; 5334 long mBindRunningAverageNs = 0; 5335 } 5336 SparseArray<ScrapData> mScrap = new SparseArray<>(); 5337 5338 private int mAttachCount = 0; 5339 5340 /** 5341 * Discard all ViewHolders. 5342 */ 5343 public void clear() { 5344 for (int i = 0; i < mScrap.size(); i++) { 5345 ScrapData data = mScrap.valueAt(i); 5346 data.mScrapHeap.clear(); 5347 } 5348 } 5349 5350 /** 5351 * Sets the maximum number of ViewHolders to hold in the pool before discarding. 5352 * 5353 * @param viewType ViewHolder Type 5354 * @param max Maximum number 5355 */ 5356 public void setMaxRecycledViews(int viewType, int max) { 5357 ScrapData scrapData = getScrapDataForType(viewType); 5358 scrapData.mMaxScrap = max; 5359 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 5360 while (scrapHeap.size() > max) { 5361 scrapHeap.remove(scrapHeap.size() - 1); 5362 } 5363 } 5364 5365 /** 5366 * Returns the current number of Views held by the RecycledViewPool of the given view type. 5367 */ 5368 public int getRecycledViewCount(int viewType) { 5369 return getScrapDataForType(viewType).mScrapHeap.size(); 5370 } 5371 5372 /** 5373 * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are 5374 * present. 5375 * 5376 * @param viewType ViewHolder type. 5377 * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none 5378 * are present. 5379 */ 5380 @Nullable 5381 public ViewHolder getRecycledView(int viewType) { 5382 final ScrapData scrapData = mScrap.get(viewType); 5383 if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { 5384 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 5385 return scrapHeap.remove(scrapHeap.size() - 1); 5386 } 5387 return null; 5388 } 5389 5390 /** 5391 * Total number of ViewHolders held by the pool. 5392 * 5393 * @return Number of ViewHolders held by the pool. 5394 */ 5395 int size() { 5396 int count = 0; 5397 for (int i = 0; i < mScrap.size(); i++) { 5398 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap; 5399 if (viewHolders != null) { 5400 count += viewHolders.size(); 5401 } 5402 } 5403 return count; 5404 } 5405 5406 /** 5407 * Add a scrap ViewHolder to the pool. 5408 * <p> 5409 * If the pool is already full for that ViewHolder's type, it will be immediately discarded. 5410 * 5411 * @param scrap ViewHolder to be added to the pool. 5412 */ 5413 public void putRecycledView(ViewHolder scrap) { 5414 final int viewType = scrap.getItemViewType(); 5415 final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; 5416 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { 5417 return; 5418 } 5419 if (DEBUG && scrapHeap.contains(scrap)) { 5420 throw new IllegalArgumentException("this scrap item already exists"); 5421 } 5422 scrap.resetInternal(); 5423 scrapHeap.add(scrap); 5424 } 5425 5426 long runningAverage(long oldAverage, long newValue) { 5427 if (oldAverage == 0) { 5428 return newValue; 5429 } 5430 return (oldAverage / 4 * 3) + (newValue / 4); 5431 } 5432 5433 void factorInCreateTime(int viewType, long createTimeNs) { 5434 ScrapData scrapData = getScrapDataForType(viewType); 5435 scrapData.mCreateRunningAverageNs = runningAverage( 5436 scrapData.mCreateRunningAverageNs, createTimeNs); 5437 } 5438 5439 void factorInBindTime(int viewType, long bindTimeNs) { 5440 ScrapData scrapData = getScrapDataForType(viewType); 5441 scrapData.mBindRunningAverageNs = runningAverage( 5442 scrapData.mBindRunningAverageNs, bindTimeNs); 5443 } 5444 5445 boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { 5446 long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; 5447 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 5448 } 5449 5450 boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { 5451 long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; 5452 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 5453 } 5454 5455 void attach(Adapter adapter) { 5456 mAttachCount++; 5457 } 5458 5459 void detach() { 5460 mAttachCount--; 5461 } 5462 5463 5464 /** 5465 * Detaches the old adapter and attaches the new one. 5466 * <p> 5467 * RecycledViewPool will clear its cache if it has only one adapter attached and the new 5468 * adapter uses a different ViewHolder than the oldAdapter. 5469 * 5470 * @param oldAdapter The previous adapter instance. Will be detached. 5471 * @param newAdapter The new adapter instance. Will be attached. 5472 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same 5473 * ViewHolder and view types. 5474 */ 5475 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 5476 boolean compatibleWithPrevious) { 5477 if (oldAdapter != null) { 5478 detach(); 5479 } 5480 if (!compatibleWithPrevious && mAttachCount == 0) { 5481 clear(); 5482 } 5483 if (newAdapter != null) { 5484 attach(newAdapter); 5485 } 5486 } 5487 5488 private ScrapData getScrapDataForType(int viewType) { 5489 ScrapData scrapData = mScrap.get(viewType); 5490 if (scrapData == null) { 5491 scrapData = new ScrapData(); 5492 mScrap.put(viewType, scrapData); 5493 } 5494 return scrapData; 5495 } 5496 } 5497 5498 /** 5499 * Utility method for finding an internal RecyclerView, if present 5500 */ 5501 @Nullable 5502 static RecyclerView findNestedRecyclerView(@NonNull View view) { 5503 if (!(view instanceof ViewGroup)) { 5504 return null; 5505 } 5506 if (view instanceof RecyclerView) { 5507 return (RecyclerView) view; 5508 } 5509 final ViewGroup parent = (ViewGroup) view; 5510 final int count = parent.getChildCount(); 5511 for (int i = 0; i < count; i++) { 5512 final View child = parent.getChildAt(i); 5513 final RecyclerView descendant = findNestedRecyclerView(child); 5514 if (descendant != null) { 5515 return descendant; 5516 } 5517 } 5518 return null; 5519 } 5520 5521 /** 5522 * Utility method for clearing holder's internal RecyclerView, if present 5523 */ 5524 static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { 5525 if (holder.mNestedRecyclerView != null) { 5526 View item = holder.mNestedRecyclerView.get(); 5527 while (item != null) { 5528 if (item == holder.itemView) { 5529 return; // match found, don't need to clear 5530 } 5531 5532 ViewParent parent = item.getParent(); 5533 if (parent instanceof View) { 5534 item = (View) parent; 5535 } else { 5536 item = null; 5537 } 5538 } 5539 holder.mNestedRecyclerView = null; // not nested 5540 } 5541 } 5542 5543 /** 5544 * Time base for deadline-aware work scheduling. Overridable for testing. 5545 * 5546 * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling 5547 * isn't relevant. 5548 */ 5549 long getNanoTime() { 5550 if (ALLOW_THREAD_GAP_WORK) { 5551 return System.nanoTime(); 5552 } else { 5553 return 0; 5554 } 5555 } 5556 5557 /** 5558 * A Recycler is responsible for managing scrapped or detached item views for reuse. 5559 * 5560 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 5561 * that has been marked for removal or reuse.</p> 5562 * 5563 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 5564 * an adapter's data set representing the data at a given position or item ID. 5565 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 5566 * If not, the view can be quickly reused by the LayoutManager with no further work. 5567 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 5568 * may be repositioned by a LayoutManager without remeasurement.</p> 5569 */ 5570 public final class Recycler { 5571 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); 5572 ArrayList<ViewHolder> mChangedScrap = null; 5573 5574 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 5575 5576 private final List<ViewHolder> 5577 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 5578 5579 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; 5580 int mViewCacheMax = DEFAULT_CACHE_SIZE; 5581 5582 RecycledViewPool mRecyclerPool; 5583 5584 private ViewCacheExtension mViewCacheExtension; 5585 5586 static final int DEFAULT_CACHE_SIZE = 2; 5587 5588 /** 5589 * Clear scrap views out of this recycler. Detached views contained within a 5590 * recycled view pool will remain. 5591 */ 5592 public void clear() { 5593 mAttachedScrap.clear(); 5594 recycleAndClearCachedViews(); 5595 } 5596 5597 /** 5598 * Set the maximum number of detached, valid views we should retain for later use. 5599 * 5600 * @param viewCount Number of views to keep before sending views to the shared pool 5601 */ 5602 public void setViewCacheSize(int viewCount) { 5603 mRequestedCacheMax = viewCount; 5604 updateViewCacheSize(); 5605 } 5606 5607 void updateViewCacheSize() { 5608 int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; 5609 mViewCacheMax = mRequestedCacheMax + extraCache; 5610 5611 // first, try the views that can be recycled 5612 for (int i = mCachedViews.size() - 1; 5613 i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { 5614 recycleCachedViewAt(i); 5615 } 5616 } 5617 5618 /** 5619 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. 5620 * 5621 * @return List of ViewHolders in the scrap list. 5622 */ 5623 @NonNull 5624 public List<ViewHolder> getScrapList() { 5625 return mUnmodifiableAttachedScrap; 5626 } 5627 5628 /** 5629 * Helper method for getViewForPosition. 5630 * <p> 5631 * Checks whether a given view holder can be used for the provided position. 5632 * 5633 * @param holder ViewHolder 5634 * @return true if ViewHolder matches the provided position, false otherwise 5635 */ 5636 boolean validateViewHolderForOffsetPosition(ViewHolder holder) { 5637 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore 5638 // if it is not removed, verify the type and id. 5639 if (holder.isRemoved()) { 5640 if (DEBUG && !mState.isPreLayout()) { 5641 throw new IllegalStateException("should not receive a removed view unless it" 5642 + " is pre layout" + exceptionLabel()); 5643 } 5644 return mState.isPreLayout(); 5645 } 5646 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { 5647 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " 5648 + "adapter position" + holder + exceptionLabel()); 5649 } 5650 if (!mState.isPreLayout()) { 5651 // don't check type if it is pre-layout. 5652 final int type = mAdapter.getItemViewType(holder.mPosition); 5653 if (type != holder.getItemViewType()) { 5654 return false; 5655 } 5656 } 5657 if (mAdapter.hasStableIds()) { 5658 return holder.getItemId() == mAdapter.getItemId(holder.mPosition); 5659 } 5660 return true; 5661 } 5662 5663 /** 5664 * Attempts to bind view, and account for relevant timing information. If 5665 * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. 5666 * 5667 * @param holder Holder to be bound. 5668 * @param offsetPosition Position of item to be bound. 5669 * @param position Pre-layout position of item to be bound. 5670 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 5671 * complete. If FOREVER_NS is passed, this method will not fail to 5672 * bind the holder. 5673 * @return 5674 */ 5675 private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, 5676 int position, long deadlineNs) { 5677 holder.mOwnerRecyclerView = RecyclerView.this; 5678 final int viewType = holder.getItemViewType(); 5679 long startBindNs = getNanoTime(); 5680 if (deadlineNs != FOREVER_NS 5681 && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { 5682 // abort - we have a deadline we can't meet 5683 return false; 5684 } 5685 mAdapter.bindViewHolder(holder, offsetPosition); 5686 long endBindNs = getNanoTime(); 5687 mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); 5688 attachAccessibilityDelegateOnBind(holder); 5689 if (mState.isPreLayout()) { 5690 holder.mPreLayoutPosition = position; 5691 } 5692 return true; 5693 } 5694 5695 /** 5696 * Binds the given View to the position. The View can be a View previously retrieved via 5697 * {@link #getViewForPosition(int)} or created by 5698 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. 5699 * <p> 5700 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} 5701 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who 5702 * wants to handle its own recycling logic. 5703 * <p> 5704 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so 5705 * you don't need to call this method unless you want to bind this View to another position. 5706 * 5707 * @param view The view to update. 5708 * @param position The position of the item to bind to this View. 5709 */ 5710 public void bindViewToPosition(@NonNull View view, int position) { 5711 ViewHolder holder = getChildViewHolderInt(view); 5712 if (holder == null) { 5713 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" 5714 + " pass arbitrary views to this method, they should be created by the " 5715 + "Adapter" + exceptionLabel()); 5716 } 5717 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5718 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 5719 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 5720 + "position " + position + "(offset:" + offsetPosition + ")." 5721 + "state:" + mState.getItemCount() + exceptionLabel()); 5722 } 5723 tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); 5724 5725 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 5726 final LayoutParams rvLayoutParams; 5727 if (lp == null) { 5728 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 5729 holder.itemView.setLayoutParams(rvLayoutParams); 5730 } else if (!checkLayoutParams(lp)) { 5731 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 5732 holder.itemView.setLayoutParams(rvLayoutParams); 5733 } else { 5734 rvLayoutParams = (LayoutParams) lp; 5735 } 5736 5737 rvLayoutParams.mInsetsDirty = true; 5738 rvLayoutParams.mViewHolder = holder; 5739 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; 5740 } 5741 5742 /** 5743 * RecyclerView provides artificial position range (item count) in pre-layout state and 5744 * automatically maps these positions to {@link Adapter} positions when 5745 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. 5746 * <p> 5747 * Usually, LayoutManager does not need to worry about this. However, in some cases, your 5748 * LayoutManager may need to call some custom component with item positions in which 5749 * case you need the actual adapter position instead of the pre layout position. You 5750 * can use this method to convert a pre-layout position to adapter (post layout) position. 5751 * <p> 5752 * Note that if the provided position belongs to a deleted ViewHolder, this method will 5753 * return -1. 5754 * <p> 5755 * Calling this method in post-layout state returns the same value back. 5756 * 5757 * @param position The pre-layout position to convert. Must be greater or equal to 0 and 5758 * less than {@link State#getItemCount()}. 5759 */ 5760 public int convertPreLayoutPositionToPostLayout(int position) { 5761 if (position < 0 || position >= mState.getItemCount()) { 5762 throw new IndexOutOfBoundsException("invalid position " + position + ". State " 5763 + "item count is " + mState.getItemCount() + exceptionLabel()); 5764 } 5765 if (!mState.isPreLayout()) { 5766 return position; 5767 } 5768 return mAdapterHelper.findPositionOffset(position); 5769 } 5770 5771 /** 5772 * Obtain a view initialized for the given position. 5773 * 5774 * This method should be used by {@link LayoutManager} implementations to obtain 5775 * views to represent data from an {@link Adapter}. 5776 * <p> 5777 * The Recycler may reuse a scrap or detached view from a shared pool if one is 5778 * available for the correct view type. If the adapter has not indicated that the 5779 * data at the given position has changed, the Recycler will attempt to hand back 5780 * a scrap view that was previously initialized for that data without rebinding. 5781 * 5782 * @param position Position to obtain a view for 5783 * @return A view representing the data at <code>position</code> from <code>adapter</code> 5784 */ 5785 @NonNull 5786 public View getViewForPosition(int position) { 5787 return getViewForPosition(position, false); 5788 } 5789 5790 View getViewForPosition(int position, boolean dryRun) { 5791 return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; 5792 } 5793 5794 /** 5795 * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, 5796 * cache, the RecycledViewPool, or creating it directly. 5797 * <p> 5798 * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return 5799 * rather than constructing or binding a ViewHolder if it doesn't think it has time. 5800 * If a ViewHolder must be constructed and not enough time remains, null is returned. If a 5801 * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is 5802 * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. 5803 * 5804 * @param position Position of ViewHolder to be returned. 5805 * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ 5806 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 5807 * complete. If FOREVER_NS is passed, this method will not fail to 5808 * create/bind the holder if needed. 5809 * 5810 * @return ViewHolder for requested position 5811 */ 5812 @Nullable 5813 ViewHolder tryGetViewHolderForPositionByDeadline(int position, 5814 boolean dryRun, long deadlineNs) { 5815 if (position < 0 || position >= mState.getItemCount()) { 5816 throw new IndexOutOfBoundsException("Invalid item position " + position 5817 + "(" + position + "). Item count:" + mState.getItemCount() 5818 + exceptionLabel()); 5819 } 5820 boolean fromScrapOrHiddenOrCache = false; 5821 ViewHolder holder = null; 5822 // 0) If there is a changed scrap, try to find from there 5823 if (mState.isPreLayout()) { 5824 holder = getChangedScrapViewForPosition(position); 5825 fromScrapOrHiddenOrCache = holder != null; 5826 } 5827 // 1) Find by position from scrap/hidden list/cache 5828 if (holder == null) { 5829 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); 5830 if (holder != null) { 5831 if (!validateViewHolderForOffsetPosition(holder)) { 5832 // recycle holder (and unscrap if relevant) since it can't be used 5833 if (!dryRun) { 5834 // we would like to recycle this but need to make sure it is not used by 5835 // animation logic etc. 5836 holder.addFlags(ViewHolder.FLAG_INVALID); 5837 if (holder.isScrap()) { 5838 removeDetachedView(holder.itemView, false); 5839 holder.unScrap(); 5840 } else if (holder.wasReturnedFromScrap()) { 5841 holder.clearReturnedFromScrapFlag(); 5842 } 5843 recycleViewHolderInternal(holder); 5844 } 5845 holder = null; 5846 } else { 5847 fromScrapOrHiddenOrCache = true; 5848 } 5849 } 5850 } 5851 if (holder == null) { 5852 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5853 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 5854 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 5855 + "position " + position + "(offset:" + offsetPosition + ")." 5856 + "state:" + mState.getItemCount() + exceptionLabel()); 5857 } 5858 5859 final int type = mAdapter.getItemViewType(offsetPosition); 5860 // 2) Find from scrap/cache via stable ids, if exists 5861 if (mAdapter.hasStableIds()) { 5862 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), 5863 type, dryRun); 5864 if (holder != null) { 5865 // update position 5866 holder.mPosition = offsetPosition; 5867 fromScrapOrHiddenOrCache = true; 5868 } 5869 } 5870 if (holder == null && mViewCacheExtension != null) { 5871 // We are NOT sending the offsetPosition because LayoutManager does not 5872 // know it. 5873 final View view = mViewCacheExtension 5874 .getViewForPositionAndType(this, position, type); 5875 if (view != null) { 5876 holder = getChildViewHolder(view); 5877 if (holder == null) { 5878 throw new IllegalArgumentException("getViewForPositionAndType returned" 5879 + " a view which does not have a ViewHolder" 5880 + exceptionLabel()); 5881 } else if (holder.shouldIgnore()) { 5882 throw new IllegalArgumentException("getViewForPositionAndType returned" 5883 + " a view that is ignored. You must call stopIgnoring before" 5884 + " returning this view." + exceptionLabel()); 5885 } 5886 } 5887 } 5888 if (holder == null) { // fallback to pool 5889 if (DEBUG) { 5890 Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" 5891 + position + ") fetching from shared pool"); 5892 } 5893 holder = getRecycledViewPool().getRecycledView(type); 5894 if (holder != null) { 5895 holder.resetInternal(); 5896 if (FORCE_INVALIDATE_DISPLAY_LIST) { 5897 invalidateDisplayListInt(holder); 5898 } 5899 } 5900 } 5901 if (holder == null) { 5902 long start = getNanoTime(); 5903 if (deadlineNs != FOREVER_NS 5904 && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { 5905 // abort - we have a deadline we can't meet 5906 return null; 5907 } 5908 holder = mAdapter.createViewHolder(RecyclerView.this, type); 5909 if (ALLOW_THREAD_GAP_WORK) { 5910 // only bother finding nested RV if prefetching 5911 RecyclerView innerView = findNestedRecyclerView(holder.itemView); 5912 if (innerView != null) { 5913 holder.mNestedRecyclerView = new WeakReference<>(innerView); 5914 } 5915 } 5916 5917 long end = getNanoTime(); 5918 mRecyclerPool.factorInCreateTime(type, end - start); 5919 if (DEBUG) { 5920 Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); 5921 } 5922 } 5923 } 5924 5925 // This is very ugly but the only place we can grab this information 5926 // before the View is rebound and returned to the LayoutManager for post layout ops. 5927 // We don't need this in pre-layout since the VH is not updated by the LM. 5928 if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder 5929 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { 5930 holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 5931 if (mState.mRunSimpleAnimations) { 5932 int changeFlags = ItemAnimator 5933 .buildAdapterChangeFlagsForAnimations(holder); 5934 changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 5935 final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, 5936 holder, changeFlags, holder.getUnmodifiedPayloads()); 5937 recordAnimationInfoIfBouncedHiddenView(holder, info); 5938 } 5939 } 5940 5941 boolean bound = false; 5942 if (mState.isPreLayout() && holder.isBound()) { 5943 // do not update unless we absolutely have to. 5944 holder.mPreLayoutPosition = position; 5945 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 5946 if (DEBUG && holder.isRemoved()) { 5947 throw new IllegalStateException("Removed holder should be bound and it should" 5948 + " come here only in pre-layout. Holder: " + holder 5949 + exceptionLabel()); 5950 } 5951 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5952 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); 5953 } 5954 5955 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 5956 final LayoutParams rvLayoutParams; 5957 if (lp == null) { 5958 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 5959 holder.itemView.setLayoutParams(rvLayoutParams); 5960 } else if (!checkLayoutParams(lp)) { 5961 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 5962 holder.itemView.setLayoutParams(rvLayoutParams); 5963 } else { 5964 rvLayoutParams = (LayoutParams) lp; 5965 } 5966 rvLayoutParams.mViewHolder = holder; 5967 rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; 5968 return holder; 5969 } 5970 5971 private void attachAccessibilityDelegateOnBind(ViewHolder holder) { 5972 if (isAccessibilityEnabled()) { 5973 final View itemView = holder.itemView; 5974 if (ViewCompat.getImportantForAccessibility(itemView) 5975 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 5976 ViewCompat.setImportantForAccessibility(itemView, 5977 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 5978 } 5979 if (!ViewCompat.hasAccessibilityDelegate(itemView)) { 5980 holder.addFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE); 5981 ViewCompat.setAccessibilityDelegate(itemView, 5982 mAccessibilityDelegate.getItemDelegate()); 5983 } 5984 } 5985 } 5986 5987 private void invalidateDisplayListInt(ViewHolder holder) { 5988 if (holder.itemView instanceof ViewGroup) { 5989 invalidateDisplayListInt((ViewGroup) holder.itemView, false); 5990 } 5991 } 5992 5993 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { 5994 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 5995 final View view = viewGroup.getChildAt(i); 5996 if (view instanceof ViewGroup) { 5997 invalidateDisplayListInt((ViewGroup) view, true); 5998 } 5999 } 6000 if (!invalidateThis) { 6001 return; 6002 } 6003 // we need to force it to become invisible 6004 if (viewGroup.getVisibility() == View.INVISIBLE) { 6005 viewGroup.setVisibility(View.VISIBLE); 6006 viewGroup.setVisibility(View.INVISIBLE); 6007 } else { 6008 final int visibility = viewGroup.getVisibility(); 6009 viewGroup.setVisibility(View.INVISIBLE); 6010 viewGroup.setVisibility(visibility); 6011 } 6012 } 6013 6014 /** 6015 * Recycle a detached view. The specified view will be added to a pool of views 6016 * for later rebinding and reuse. 6017 * 6018 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the 6019 * View is scrapped, it will be removed from scrap list.</p> 6020 * 6021 * @param view Removed view for recycling 6022 * @see LayoutManager#removeAndRecycleView(View, Recycler) 6023 */ 6024 public void recycleView(@NonNull View view) { 6025 // This public recycle method tries to make view recycle-able since layout manager 6026 // intended to recycle this view (e.g. even if it is in scrap or change cache) 6027 ViewHolder holder = getChildViewHolderInt(view); 6028 if (holder.isTmpDetached()) { 6029 removeDetachedView(view, false); 6030 } 6031 if (holder.isScrap()) { 6032 holder.unScrap(); 6033 } else if (holder.wasReturnedFromScrap()) { 6034 holder.clearReturnedFromScrapFlag(); 6035 } 6036 recycleViewHolderInternal(holder); 6037 } 6038 6039 /** 6040 * Internally, use this method instead of {@link #recycleView(android.view.View)} to 6041 * catch potential bugs. 6042 * @param view 6043 */ 6044 void recycleViewInternal(View view) { 6045 recycleViewHolderInternal(getChildViewHolderInt(view)); 6046 } 6047 6048 void recycleAndClearCachedViews() { 6049 final int count = mCachedViews.size(); 6050 for (int i = count - 1; i >= 0; i--) { 6051 recycleCachedViewAt(i); 6052 } 6053 mCachedViews.clear(); 6054 if (ALLOW_THREAD_GAP_WORK) { 6055 mPrefetchRegistry.clearPrefetchPositions(); 6056 } 6057 } 6058 6059 /** 6060 * Recycles a cached view and removes the view from the list. Views are added to cache 6061 * if and only if they are recyclable, so this method does not check it again. 6062 * <p> 6063 * A small exception to this rule is when the view does not have an animator reference 6064 * but transient state is true (due to animations created outside ItemAnimator). In that 6065 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is 6066 * still recyclable since Adapter wants to do so. 6067 * 6068 * @param cachedViewIndex The index of the view in cached views list 6069 */ 6070 void recycleCachedViewAt(int cachedViewIndex) { 6071 if (DEBUG) { 6072 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); 6073 } 6074 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 6075 if (DEBUG) { 6076 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); 6077 } 6078 addViewHolderToRecycledViewPool(viewHolder, true); 6079 mCachedViews.remove(cachedViewIndex); 6080 } 6081 6082 /** 6083 * internal implementation checks if view is scrapped or attached and throws an exception 6084 * if so. 6085 * Public version un-scraps before calling recycle. 6086 */ 6087 void recycleViewHolderInternal(ViewHolder holder) { 6088 if (holder.isScrap() || holder.itemView.getParent() != null) { 6089 throw new IllegalArgumentException( 6090 "Scrapped or attached views may not be recycled. isScrap:" 6091 + holder.isScrap() + " isAttached:" 6092 + (holder.itemView.getParent() != null) + exceptionLabel()); 6093 } 6094 6095 if (holder.isTmpDetached()) { 6096 throw new IllegalArgumentException("Tmp detached view should be removed " 6097 + "from RecyclerView before it can be recycled: " + holder 6098 + exceptionLabel()); 6099 } 6100 6101 if (holder.shouldIgnore()) { 6102 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" 6103 + " should first call stopIgnoringView(view) before calling recycle." 6104 + exceptionLabel()); 6105 } 6106 //noinspection unchecked 6107 final boolean transientStatePreventsRecycling = holder 6108 .doesTransientStatePreventRecycling(); 6109 final boolean forceRecycle = mAdapter != null 6110 && transientStatePreventsRecycling 6111 && mAdapter.onFailedToRecycleView(holder); 6112 boolean cached = false; 6113 boolean recycled = false; 6114 if (DEBUG && mCachedViews.contains(holder)) { 6115 throw new IllegalArgumentException("cached view received recycle internal? " 6116 + holder + exceptionLabel()); 6117 } 6118 if (forceRecycle || holder.isRecyclable()) { 6119 if (mViewCacheMax > 0 6120 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 6121 | ViewHolder.FLAG_REMOVED 6122 | ViewHolder.FLAG_UPDATE 6123 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { 6124 // Retire oldest cached view 6125 int cachedViewSize = mCachedViews.size(); 6126 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { 6127 recycleCachedViewAt(0); 6128 cachedViewSize--; 6129 } 6130 6131 int targetCacheIndex = cachedViewSize; 6132 if (ALLOW_THREAD_GAP_WORK 6133 && cachedViewSize > 0 6134 && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { 6135 // when adding the view, skip past most recently prefetched views 6136 int cacheIndex = cachedViewSize - 1; 6137 while (cacheIndex >= 0) { 6138 int cachedPos = mCachedViews.get(cacheIndex).mPosition; 6139 if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { 6140 break; 6141 } 6142 cacheIndex--; 6143 } 6144 targetCacheIndex = cacheIndex + 1; 6145 } 6146 mCachedViews.add(targetCacheIndex, holder); 6147 cached = true; 6148 } 6149 if (!cached) { 6150 addViewHolderToRecycledViewPool(holder, true); 6151 recycled = true; 6152 } 6153 } else { 6154 // NOTE: A view can fail to be recycled when it is scrolled off while an animation 6155 // runs. In this case, the item is eventually recycled by 6156 // ItemAnimatorRestoreListener#onAnimationFinished. 6157 6158 // TODO: consider cancelling an animation when an item is removed scrollBy, 6159 // to return it to the pool faster 6160 if (DEBUG) { 6161 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " 6162 + "re-visit here. We are still removing it from animation lists" 6163 + exceptionLabel()); 6164 } 6165 } 6166 // even if the holder is not removed, we still call this method so that it is removed 6167 // from view holder lists. 6168 mViewInfoStore.removeViewHolder(holder); 6169 if (!cached && !recycled && transientStatePreventsRecycling) { 6170 holder.mOwnerRecyclerView = null; 6171 } 6172 } 6173 6174 /** 6175 * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. 6176 * 6177 * Pass false to dispatchRecycled for views that have not been bound. 6178 * 6179 * @param holder Holder to be added to the pool. 6180 * @param dispatchRecycled True to dispatch View recycled callbacks. 6181 */ 6182 void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { 6183 clearNestedRecyclerViewIfNotNested(holder); 6184 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) { 6185 holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE); 6186 ViewCompat.setAccessibilityDelegate(holder.itemView, null); 6187 } 6188 if (dispatchRecycled) { 6189 dispatchViewRecycled(holder); 6190 } 6191 holder.mOwnerRecyclerView = null; 6192 getRecycledViewPool().putRecycledView(holder); 6193 } 6194 6195 /** 6196 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 6197 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 6198 * internal bookkeeping. 6199 */ 6200 void quickRecycleScrapView(View view) { 6201 final ViewHolder holder = getChildViewHolderInt(view); 6202 holder.mScrapContainer = null; 6203 holder.mInChangeScrap = false; 6204 holder.clearReturnedFromScrapFlag(); 6205 recycleViewHolderInternal(holder); 6206 } 6207 6208 /** 6209 * Mark an attached view as scrap. 6210 * 6211 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 6212 * for rebinding and reuse. Requests for a view for a given position may return a 6213 * reused or rebound scrap view instance.</p> 6214 * 6215 * @param view View to scrap 6216 */ 6217 void scrapView(View view) { 6218 final ViewHolder holder = getChildViewHolderInt(view); 6219 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) 6220 || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { 6221 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { 6222 throw new IllegalArgumentException("Called scrap view with an invalid view." 6223 + " Invalid views cannot be reused from scrap, they should rebound from" 6224 + " recycler pool." + exceptionLabel()); 6225 } 6226 holder.setScrapContainer(this, false); 6227 mAttachedScrap.add(holder); 6228 } else { 6229 if (mChangedScrap == null) { 6230 mChangedScrap = new ArrayList<ViewHolder>(); 6231 } 6232 holder.setScrapContainer(this, true); 6233 mChangedScrap.add(holder); 6234 } 6235 } 6236 6237 /** 6238 * Remove a previously scrapped view from the pool of eligible scrap. 6239 * 6240 * <p>This view will no longer be eligible for reuse until re-scrapped or 6241 * until it is explicitly removed and recycled.</p> 6242 */ 6243 void unscrapView(ViewHolder holder) { 6244 if (holder.mInChangeScrap) { 6245 mChangedScrap.remove(holder); 6246 } else { 6247 mAttachedScrap.remove(holder); 6248 } 6249 holder.mScrapContainer = null; 6250 holder.mInChangeScrap = false; 6251 holder.clearReturnedFromScrapFlag(); 6252 } 6253 6254 int getScrapCount() { 6255 return mAttachedScrap.size(); 6256 } 6257 6258 View getScrapViewAt(int index) { 6259 return mAttachedScrap.get(index).itemView; 6260 } 6261 6262 void clearScrap() { 6263 mAttachedScrap.clear(); 6264 if (mChangedScrap != null) { 6265 mChangedScrap.clear(); 6266 } 6267 } 6268 6269 ViewHolder getChangedScrapViewForPosition(int position) { 6270 // If pre-layout, check the changed scrap for an exact match. 6271 final int changedScrapSize; 6272 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { 6273 return null; 6274 } 6275 // find by position 6276 for (int i = 0; i < changedScrapSize; i++) { 6277 final ViewHolder holder = mChangedScrap.get(i); 6278 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { 6279 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6280 return holder; 6281 } 6282 } 6283 // find by id 6284 if (mAdapter.hasStableIds()) { 6285 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 6286 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { 6287 final long id = mAdapter.getItemId(offsetPosition); 6288 for (int i = 0; i < changedScrapSize; i++) { 6289 final ViewHolder holder = mChangedScrap.get(i); 6290 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { 6291 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6292 return holder; 6293 } 6294 } 6295 } 6296 } 6297 return null; 6298 } 6299 6300 /** 6301 * Returns a view for the position either from attach scrap, hidden children, or cache. 6302 * 6303 * @param position Item position 6304 * @param dryRun Does a dry run, finds the ViewHolder but does not remove 6305 * @return a ViewHolder that can be re-used for this position. 6306 */ 6307 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { 6308 final int scrapCount = mAttachedScrap.size(); 6309 6310 // Try first for an exact, non-invalid match from scrap. 6311 for (int i = 0; i < scrapCount; i++) { 6312 final ViewHolder holder = mAttachedScrap.get(i); 6313 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position 6314 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { 6315 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6316 return holder; 6317 } 6318 } 6319 6320 if (!dryRun) { 6321 View view = mChildHelper.findHiddenNonRemovedView(position); 6322 if (view != null) { 6323 // This View is good to be used. We just need to unhide, detach and move to the 6324 // scrap list. 6325 final ViewHolder vh = getChildViewHolderInt(view); 6326 mChildHelper.unhide(view); 6327 int layoutIndex = mChildHelper.indexOfChild(view); 6328 if (layoutIndex == RecyclerView.NO_POSITION) { 6329 throw new IllegalStateException("layout index should not be -1 after " 6330 + "unhiding a view:" + vh + exceptionLabel()); 6331 } 6332 mChildHelper.detachViewFromParent(layoutIndex); 6333 scrapView(view); 6334 vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP 6335 | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 6336 return vh; 6337 } 6338 } 6339 6340 // Search in our first-level recycled view cache. 6341 final int cacheSize = mCachedViews.size(); 6342 for (int i = 0; i < cacheSize; i++) { 6343 final ViewHolder holder = mCachedViews.get(i); 6344 // invalid view holders may be in cache if adapter has stable ids as they can be 6345 // retrieved via getScrapOrCachedViewForId 6346 if (!holder.isInvalid() && holder.getLayoutPosition() == position) { 6347 if (!dryRun) { 6348 mCachedViews.remove(i); 6349 } 6350 if (DEBUG) { 6351 Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position 6352 + ") found match in cache: " + holder); 6353 } 6354 return holder; 6355 } 6356 } 6357 return null; 6358 } 6359 6360 ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { 6361 // Look in our attached views first 6362 final int count = mAttachedScrap.size(); 6363 for (int i = count - 1; i >= 0; i--) { 6364 final ViewHolder holder = mAttachedScrap.get(i); 6365 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { 6366 if (type == holder.getItemViewType()) { 6367 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6368 if (holder.isRemoved()) { 6369 // this might be valid in two cases: 6370 // > item is removed but we are in pre-layout pass 6371 // >> do nothing. return as is. make sure we don't rebind 6372 // > item is removed then added to another position and we are in 6373 // post layout. 6374 // >> remove removed and invalid flags, add update flag to rebind 6375 // because item was invisible to us and we don't know what happened in 6376 // between. 6377 if (!mState.isPreLayout()) { 6378 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE 6379 | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); 6380 } 6381 } 6382 return holder; 6383 } else if (!dryRun) { 6384 // if we are running animations, it is actually better to keep it in scrap 6385 // but this would force layout manager to lay it out which would be bad. 6386 // Recycle this scrap. Type mismatch. 6387 mAttachedScrap.remove(i); 6388 removeDetachedView(holder.itemView, false); 6389 quickRecycleScrapView(holder.itemView); 6390 } 6391 } 6392 } 6393 6394 // Search the first-level cache 6395 final int cacheSize = mCachedViews.size(); 6396 for (int i = cacheSize - 1; i >= 0; i--) { 6397 final ViewHolder holder = mCachedViews.get(i); 6398 if (holder.getItemId() == id) { 6399 if (type == holder.getItemViewType()) { 6400 if (!dryRun) { 6401 mCachedViews.remove(i); 6402 } 6403 return holder; 6404 } else if (!dryRun) { 6405 recycleCachedViewAt(i); 6406 return null; 6407 } 6408 } 6409 } 6410 return null; 6411 } 6412 6413 void dispatchViewRecycled(@NonNull ViewHolder holder) { 6414 if (mRecyclerListener != null) { 6415 mRecyclerListener.onViewRecycled(holder); 6416 } 6417 if (mAdapter != null) { 6418 mAdapter.onViewRecycled(holder); 6419 } 6420 if (mState != null) { 6421 mViewInfoStore.removeViewHolder(holder); 6422 } 6423 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); 6424 } 6425 6426 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 6427 boolean compatibleWithPrevious) { 6428 clear(); 6429 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); 6430 } 6431 6432 void offsetPositionRecordsForMove(int from, int to) { 6433 final int start, end, inBetweenOffset; 6434 if (from < to) { 6435 start = from; 6436 end = to; 6437 inBetweenOffset = -1; 6438 } else { 6439 start = to; 6440 end = from; 6441 inBetweenOffset = 1; 6442 } 6443 final int cachedCount = mCachedViews.size(); 6444 for (int i = 0; i < cachedCount; i++) { 6445 final ViewHolder holder = mCachedViews.get(i); 6446 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 6447 continue; 6448 } 6449 if (holder.mPosition == from) { 6450 holder.offsetPosition(to - from, false); 6451 } else { 6452 holder.offsetPosition(inBetweenOffset, false); 6453 } 6454 if (DEBUG) { 6455 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " 6456 + holder); 6457 } 6458 } 6459 } 6460 6461 void offsetPositionRecordsForInsert(int insertedAt, int count) { 6462 final int cachedCount = mCachedViews.size(); 6463 for (int i = 0; i < cachedCount; i++) { 6464 final ViewHolder holder = mCachedViews.get(i); 6465 if (holder != null && holder.mPosition >= insertedAt) { 6466 if (DEBUG) { 6467 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " 6468 + holder + " now at position " + (holder.mPosition + count)); 6469 } 6470 holder.offsetPosition(count, true); 6471 } 6472 } 6473 } 6474 6475 /** 6476 * @param removedFrom Remove start index 6477 * @param count Remove count 6478 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if 6479 * false, they'll be applied before the second layout pass 6480 */ 6481 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { 6482 final int removedEnd = removedFrom + count; 6483 final int cachedCount = mCachedViews.size(); 6484 for (int i = cachedCount - 1; i >= 0; i--) { 6485 final ViewHolder holder = mCachedViews.get(i); 6486 if (holder != null) { 6487 if (holder.mPosition >= removedEnd) { 6488 if (DEBUG) { 6489 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i 6490 + " holder " + holder + " now at position " 6491 + (holder.mPosition - count)); 6492 } 6493 holder.offsetPosition(-count, applyToPreLayout); 6494 } else if (holder.mPosition >= removedFrom) { 6495 // Item for this view was removed. Dump it from the cache. 6496 holder.addFlags(ViewHolder.FLAG_REMOVED); 6497 recycleCachedViewAt(i); 6498 } 6499 } 6500 } 6501 } 6502 6503 void setViewCacheExtension(ViewCacheExtension extension) { 6504 mViewCacheExtension = extension; 6505 } 6506 6507 void setRecycledViewPool(RecycledViewPool pool) { 6508 if (mRecyclerPool != null) { 6509 mRecyclerPool.detach(); 6510 } 6511 mRecyclerPool = pool; 6512 if (pool != null) { 6513 mRecyclerPool.attach(getAdapter()); 6514 } 6515 } 6516 6517 RecycledViewPool getRecycledViewPool() { 6518 if (mRecyclerPool == null) { 6519 mRecyclerPool = new RecycledViewPool(); 6520 } 6521 return mRecyclerPool; 6522 } 6523 6524 void viewRangeUpdate(int positionStart, int itemCount) { 6525 final int positionEnd = positionStart + itemCount; 6526 final int cachedCount = mCachedViews.size(); 6527 for (int i = cachedCount - 1; i >= 0; i--) { 6528 final ViewHolder holder = mCachedViews.get(i); 6529 if (holder == null) { 6530 continue; 6531 } 6532 6533 final int pos = holder.mPosition; 6534 if (pos >= positionStart && pos < positionEnd) { 6535 holder.addFlags(ViewHolder.FLAG_UPDATE); 6536 recycleCachedViewAt(i); 6537 // cached views should not be flagged as changed because this will cause them 6538 // to animate when they are returned from cache. 6539 } 6540 } 6541 } 6542 6543 void markKnownViewsInvalid() { 6544 final int cachedCount = mCachedViews.size(); 6545 for (int i = 0; i < cachedCount; i++) { 6546 final ViewHolder holder = mCachedViews.get(i); 6547 if (holder != null) { 6548 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 6549 holder.addChangePayload(null); 6550 } 6551 } 6552 6553 if (mAdapter == null || !mAdapter.hasStableIds()) { 6554 // we cannot re-use cached views in this case. Recycle them all 6555 recycleAndClearCachedViews(); 6556 } 6557 } 6558 6559 void clearOldPositions() { 6560 final int cachedCount = mCachedViews.size(); 6561 for (int i = 0; i < cachedCount; i++) { 6562 final ViewHolder holder = mCachedViews.get(i); 6563 holder.clearOldPosition(); 6564 } 6565 final int scrapCount = mAttachedScrap.size(); 6566 for (int i = 0; i < scrapCount; i++) { 6567 mAttachedScrap.get(i).clearOldPosition(); 6568 } 6569 if (mChangedScrap != null) { 6570 final int changedScrapCount = mChangedScrap.size(); 6571 for (int i = 0; i < changedScrapCount; i++) { 6572 mChangedScrap.get(i).clearOldPosition(); 6573 } 6574 } 6575 } 6576 6577 void markItemDecorInsetsDirty() { 6578 final int cachedCount = mCachedViews.size(); 6579 for (int i = 0; i < cachedCount; i++) { 6580 final ViewHolder holder = mCachedViews.get(i); 6581 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); 6582 if (layoutParams != null) { 6583 layoutParams.mInsetsDirty = true; 6584 } 6585 } 6586 } 6587 } 6588 6589 /** 6590 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can 6591 * be controlled by the developer. 6592 * <p> 6593 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and 6594 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will 6595 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking 6596 * {@link RecycledViewPool}. 6597 * <p> 6598 * Note that, Recycler never sends Views to this method to be cached. It is developers 6599 * responsibility to decide whether they want to keep their Views in this custom cache or let 6600 * the default recycling policy handle it. 6601 */ 6602 public abstract static class ViewCacheExtension { 6603 6604 /** 6605 * Returns a View that can be binded to the given Adapter position. 6606 * <p> 6607 * This method should <b>not</b> create a new View. Instead, it is expected to return 6608 * an already created View that can be re-used for the given type and position. 6609 * If the View is marked as ignored, it should first call 6610 * {@link LayoutManager#stopIgnoringView(View)} before returning the View. 6611 * <p> 6612 * RecyclerView will re-bind the returned View to the position if necessary. 6613 * 6614 * @param recycler The Recycler that can be used to bind the View 6615 * @param position The adapter position 6616 * @param type The type of the View, defined by adapter 6617 * @return A View that is bound to the given position or NULL if there is no View to re-use 6618 * @see LayoutManager#ignoreView(View) 6619 */ 6620 @Nullable 6621 public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, 6622 int type); 6623 } 6624 6625 /** 6626 * Base class for an Adapter 6627 * 6628 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 6629 * within a {@link RecyclerView}.</p> 6630 * 6631 * @paramA class that extends ViewHolder that will be used by the adapter. 6632 */ 6633 public abstract static class Adapter<VH extends ViewHolder> { 6634 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 6635 private boolean mHasStableIds = false; 6636 6637 /** 6638 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent 6639 * an item. 6640 * <p> 6641 * This new ViewHolder should be constructed with a new View that can represent the items 6642 * of the given type. You can either create a new View manually or inflate it from an XML 6643 * layout file. 6644 * <p> 6645 * The new ViewHolder will be used to display items of the adapter using 6646 * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display 6647 * different items in the data set, it is a good idea to cache references to sub views of 6648 * the View to avoid unnecessary {@link View#findViewById(int)} calls. 6649 * 6650 * @param parent The ViewGroup into which the new View will be added after it is bound to 6651 * an adapter position. 6652 * @param viewType The view type of the new View. 6653 * 6654 * @return A new ViewHolder that holds a View of the given view type. 6655 * @see #getItemViewType(int) 6656 * @see #onBindViewHolder(ViewHolder, int) 6657 */ 6658 @NonNull 6659 public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); 6660 6661 /** 6662 * Called by RecyclerView to display the data at the specified position. This method should 6663 * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given 6664 * position. 6665 * <p> 6666 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 6667 * again if the position of the item changes in the data set unless the item itself is 6668 * invalidated or the new position cannot be determined. For this reason, you should only 6669 * use the <code>position</code> parameter while acquiring the related data item inside 6670 * this method and should not keep a copy of it. If you need the position of an item later 6671 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will 6672 * have the updated adapter position. 6673 * 6674 * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can 6675 * handle efficient partial bind. 6676 * 6677 * @param holder The ViewHolder which should be updated to represent the contents of the 6678 * item at the given position in the data set. 6679 * @param position The position of the item within the adapter's data set. 6680 */ 6681 public abstract void onBindViewHolder(@NonNull VH holder, int position); 6682 6683 /** 6684 * Called by RecyclerView to display the data at the specified position. This method 6685 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at 6686 * the given position. 6687 * <p> 6688 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 6689 * again if the position of the item changes in the data set unless the item itself is 6690 * invalidated or the new position cannot be determined. For this reason, you should only 6691 * use the <code>position</code> parameter while acquiring the related data item inside 6692 * this method and should not keep a copy of it. If you need the position of an item later 6693 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will 6694 * have the updated adapter position. 6695 * <p> 6696 * Partial bind vs full bind: 6697 * <p> 6698 * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or 6699 * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, 6700 * the ViewHolder is currently bound to old data and Adapter may run an efficient partial 6701 * update using the payload info. If the payload is empty, Adapter must run a full bind. 6702 * Adapter should not assume that the payload passed in notify methods will be received by 6703 * onBindViewHolder(). For example when the view is not attached to the screen, the 6704 * payload in notifyItemChange() will be simply dropped. 6705 * 6706 * @param holder The ViewHolder which should be updated to represent the contents of the 6707 * item at the given position in the data set. 6708 * @param position The position of the item within the adapter's data set. 6709 * @param payloads A non-null list of merged payloads. Can be empty list if requires full 6710 * update. 6711 */ 6712 public void onBindViewHolder(@NonNull VH holder, int position, 6713 @NonNull List<Object> payloads) { 6714 onBindViewHolder(holder, position); 6715 } 6716 6717 /** 6718 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new 6719 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. 6720 * 6721 * @see #onCreateViewHolder(ViewGroup, int) 6722 */ 6723 @NonNull 6724 public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { 6725 try { 6726 TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); 6727 final VH holder = onCreateViewHolder(parent, viewType); 6728 if (holder.itemView.getParent() != null) { 6729 throw new IllegalStateException("ViewHolder views must not be attached when" 6730 + " created. Ensure that you are not passing 'true' to the attachToRoot" 6731 + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); 6732 } 6733 holder.mItemViewType = viewType; 6734 return holder; 6735 } finally { 6736 TraceCompat.endSection(); 6737 } 6738 } 6739 6740 /** 6741 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the 6742 * {@link ViewHolder} contents with the item at the given position and also sets up some 6743 * private fields to be used by RecyclerView. 6744 * 6745 * @see #onBindViewHolder(ViewHolder, int) 6746 */ 6747 public final void bindViewHolder(@NonNull VH holder, int position) { 6748 holder.mPosition = position; 6749 if (hasStableIds()) { 6750 holder.mItemId = getItemId(position); 6751 } 6752 holder.setFlags(ViewHolder.FLAG_BOUND, 6753 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID 6754 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 6755 TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); 6756 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); 6757 holder.clearPayload(); 6758 final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); 6759 if (layoutParams instanceof RecyclerView.LayoutParams) { 6760 ((LayoutParams) layoutParams).mInsetsDirty = true; 6761 } 6762 TraceCompat.endSection(); 6763 } 6764 6765 /** 6766 * Return the view type of the item at <code>position</code> for the purposes 6767 * of view recycling. 6768 * 6769 * <p>The default implementation of this method returns 0, making the assumption of 6770 * a single view type for the adapter. Unlike ListView adapters, types need not 6771 * be contiguous. Consider using id resources to uniquely identify item view types. 6772 * 6773 * @param position position to query 6774 * @return integer value identifying the type of the view needed to represent the item at 6775 * <code>position</code>. Type codes need not be contiguous. 6776 */ 6777 public int getItemViewType(int position) { 6778 return 0; 6779 } 6780 6781 /** 6782 * Indicates whether each item in the data set can be represented with a unique identifier 6783 * of type {@link java.lang.Long}. 6784 * 6785 * @param hasStableIds Whether items in data set have unique identifiers or not. 6786 * @see #hasStableIds() 6787 * @see #getItemId(int) 6788 */ 6789 public void setHasStableIds(boolean hasStableIds) { 6790 if (hasObservers()) { 6791 throw new IllegalStateException("Cannot change whether this adapter has " 6792 + "stable IDs while the adapter has registered observers."); 6793 } 6794 mHasStableIds = hasStableIds; 6795 } 6796 6797 /** 6798 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 6799 * would return false this method should return {@link #NO_ID}. The default implementation 6800 * of this method returns {@link #NO_ID}. 6801 * 6802 * @param position Adapter position to query 6803 * @return the stable ID of the item at position 6804 */ 6805 public long getItemId(int position) { 6806 return NO_ID; 6807 } 6808 6809 /** 6810 * Returns the total number of items in the data set held by the adapter. 6811 * 6812 * @return The total number of items in this adapter. 6813 */ 6814 public abstract int getItemCount(); 6815 6816 /** 6817 * Returns true if this adapter publishes a unique <code>long</code> value that can 6818 * act as a key for the item at a given position in the data set. If that item is relocated 6819 * in the data set, the ID returned for that item should be the same. 6820 * 6821 * @return true if this adapter's items have stable IDs 6822 */ 6823 public final boolean hasStableIds() { 6824 return mHasStableIds; 6825 } 6826 6827 /** 6828 * Called when a view created by this adapter has been recycled. 6829 * 6830 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 6831 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 6832 * fallen out of visibility or a set of cached views represented by views still 6833 * attached to the parent RecyclerView. If an item view has large or expensive data 6834 * bound to it such as large bitmaps, this may be a good place to release those 6835 * resources.</p> 6836 * <p> 6837 * RecyclerView calls this method right before clearing ViewHolder's internal data and 6838 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 6839 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 6840 * its adapter position. 6841 * 6842 * @param holder The ViewHolder for the view being recycled 6843 */ 6844 public void onViewRecycled(@NonNull VH holder) { 6845 } 6846 6847 /** 6848 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled 6849 * due to its transient state. Upon receiving this callback, Adapter can clear the 6850 * animation(s) that effect the View's transient state and return <code>true</code> so that 6851 * the View can be recycled. Keep in mind that the View in question is already removed from 6852 * the RecyclerView. 6853 * <p> 6854 * In some cases, it is acceptable to recycle a View although it has transient state. Most 6855 * of the time, this is a case where the transient state will be cleared in 6856 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. 6857 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return 6858 * value of this method to decide whether the View should be recycled or not. 6859 * <p> 6860 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you 6861 * should never receive this callback because RecyclerView keeps those Views as children 6862 * until their animations are complete. This callback is useful when children of the item 6863 * views create animations which may not be easy to implement using an {@link ItemAnimator}. 6864 * <p> 6865 * You should <em>never</em> fix this issue by calling 6866 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called 6867 * <code>holder.itemView.setHasTransientState(true);</code>. Each 6868 * <code>View.setHasTransientState(true)</code> call must be matched by a 6869 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View 6870 * may become inconsistent. You should always prefer to end or cancel animations that are 6871 * triggering the transient state instead of handling it manually. 6872 * 6873 * @param holder The ViewHolder containing the View that could not be recycled due to its 6874 * transient state. 6875 * @return True if the View should be recycled, false otherwise. Note that if this method 6876 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of 6877 * the View and recycle it regardless. If this method returns <code>false</code>, 6878 * RecyclerView will check the View's transient state again before giving a final decision. 6879 * Default implementation returns false. 6880 */ 6881 public boolean onFailedToRecycleView(@NonNull VH holder) { 6882 return false; 6883 } 6884 6885 /** 6886 * Called when a view created by this adapter has been attached to a window. 6887 * 6888 * <p>This can be used as a reasonable signal that the view is about to be seen 6889 * by the user. If the adapter previously freed any resources in 6890 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 6891 * those resources should be restored here.</p> 6892 * 6893 * @param holder Holder of the view being attached 6894 */ 6895 public void onViewAttachedToWindow(@NonNull VH holder) { 6896 } 6897 6898 /** 6899 * Called when a view created by this adapter has been detached from its window. 6900 * 6901 * <p>Becoming detached from the window is not necessarily a permanent condition; 6902 * the consumer of an Adapter's views may choose to cache views offscreen while they 6903 * are not visible, attaching and detaching them as appropriate.</p> 6904 * 6905 * @param holder Holder of the view being detached 6906 */ 6907 public void onViewDetachedFromWindow(@NonNull VH holder) { 6908 } 6909 6910 /** 6911 * Returns true if one or more observers are attached to this adapter. 6912 * 6913 * @return true if this adapter has observers 6914 */ 6915 public final boolean hasObservers() { 6916 return mObservable.hasObservers(); 6917 } 6918 6919 /** 6920 * Register a new observer to listen for data changes. 6921 * 6922 * <p>The adapter may publish a variety of events describing specific changes. 6923 * Not all adapters may support all change types and some may fall back to a generic 6924 * {@link RecyclerView.AdapterDataObserver#onChanged() 6925 * "something changed"} event if more specific data is not available.</p> 6926 * 6927 * <p>Components registering observers with an adapter are responsible for 6928 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 6929 * unregistering} those observers when finished.</p> 6930 * 6931 * @param observer Observer to register 6932 * 6933 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 6934 */ 6935 public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { 6936 mObservable.registerObserver(observer); 6937 } 6938 6939 /** 6940 * Unregister an observer currently listening for data changes. 6941 * 6942 * <p>The unregistered observer will no longer receive events about changes 6943 * to the adapter.</p> 6944 * 6945 * @param observer Observer to unregister 6946 * 6947 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) 6948 */ 6949 public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { 6950 mObservable.unregisterObserver(observer); 6951 } 6952 6953 /** 6954 * Called by RecyclerView when it starts observing this Adapter. 6955 * <p> 6956 * Keep in mind that same adapter may be observed by multiple RecyclerViews. 6957 * 6958 * @param recyclerView The RecyclerView instance which started observing this adapter. 6959 * @see #onDetachedFromRecyclerView(RecyclerView) 6960 */ 6961 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 6962 } 6963 6964 /** 6965 * Called by RecyclerView when it stops observing this Adapter. 6966 * 6967 * @param recyclerView The RecyclerView instance which stopped observing this adapter. 6968 * @see #onAttachedToRecyclerView(RecyclerView) 6969 */ 6970 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 6971 } 6972 6973 /** 6974 * Notify any registered observers that the data set has changed. 6975 * 6976 * <p>There are two different classes of data change events, item changes and structural 6977 * changes. Item changes are when a single item has its data updated but no positional 6978 * changes have occurred. Structural changes are when items are inserted, removed or moved 6979 * within the data set.</p> 6980 * 6981 * <p>This event does not specify what about the data set has changed, forcing 6982 * any observers to assume that all existing items and structure may no longer be valid. 6983 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 6984 * 6985 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 6986 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 6987 * this method is used. This can help for the purposes of animation and visual 6988 * object persistence but individual item views will still need to be rebound 6989 * and relaid out.</p> 6990 * 6991 * <p>If you are writing an adapter it will always be more efficient to use the more 6992 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 6993 * as a last resort.</p> 6994 * 6995 * @see #notifyItemChanged(int) 6996 * @see #notifyItemInserted(int) 6997 * @see #notifyItemRemoved(int) 6998 * @see #notifyItemRangeChanged(int, int) 6999 * @see #notifyItemRangeInserted(int, int) 7000 * @see #notifyItemRangeRemoved(int, int) 7001 */ 7002 public final void notifyDataSetChanged() { 7003 mObservable.notifyChanged(); 7004 } 7005 7006 /** 7007 * Notify any registered observers that the item at <code>position</code> has changed. 7008 * Equivalent to calling <code>notifyItemChanged(position, null);</code>. 7009 * 7010 * <p>This is an item change event, not a structural change event. It indicates that any 7011 * reflection of the data at <code>position</code> is out of date and should be updated. 7012 * The item at <code>position</code> retains the same identity.</p> 7013 * 7014 * @param position Position of the item that has changed 7015 * 7016 * @see #notifyItemRangeChanged(int, int) 7017 */ 7018 public final void notifyItemChanged(int position) { 7019 mObservable.notifyItemRangeChanged(position, 1); 7020 } 7021 7022 /** 7023 * Notify any registered observers that the item at <code>position</code> has changed with 7024 * an optional payload object. 7025 * 7026 * <p>This is an item change event, not a structural change event. It indicates that any 7027 * reflection of the data at <code>position</code> is out of date and should be updated. 7028 * The item at <code>position</code> retains the same identity. 7029 * </p> 7030 * 7031 * <p> 7032 * Client can optionally pass a payload for partial change. These payloads will be merged 7033 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 7034 * item is already represented by a ViewHolder and it will be rebound to the same 7035 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 7036 * payloads on that item and prevent future payload until 7037 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 7038 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 7039 * attached, the payload will be simply dropped. 7040 * 7041 * @param position Position of the item that has changed 7042 * @param payload Optional parameter, use null to identify a "full" update 7043 * 7044 * @see #notifyItemRangeChanged(int, int) 7045 */ 7046 public final void notifyItemChanged(int position, @Nullable Object payload) { 7047 mObservable.notifyItemRangeChanged(position, 1, payload); 7048 } 7049 7050 /** 7051 * Notify any registered observers that the <code>itemCount</code> items starting at 7052 * position <code>positionStart</code> have changed. 7053 * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>. 7054 * 7055 * <p>This is an item change event, not a structural change event. It indicates that 7056 * any reflection of the data in the given position range is out of date and should 7057 * be updated. The items in the given range retain the same identity.</p> 7058 * 7059 * @param positionStart Position of the first item that has changed 7060 * @param itemCount Number of items that have changed 7061 * 7062 * @see #notifyItemChanged(int) 7063 */ 7064 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 7065 mObservable.notifyItemRangeChanged(positionStart, itemCount); 7066 } 7067 7068 /** 7069 * Notify any registered observers that the <code>itemCount</code> items starting at 7070 * position <code>positionStart</code> have changed. An optional payload can be 7071 * passed to each changed item. 7072 * 7073 * <p>This is an item change event, not a structural change event. It indicates that any 7074 * reflection of the data in the given position range is out of date and should be updated. 7075 * The items in the given range retain the same identity. 7076 * </p> 7077 * 7078 * <p> 7079 * Client can optionally pass a payload for partial change. These payloads will be merged 7080 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 7081 * item is already represented by a ViewHolder and it will be rebound to the same 7082 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 7083 * payloads on that item and prevent future payload until 7084 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 7085 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 7086 * attached, the payload will be simply dropped. 7087 * 7088 * @param positionStart Position of the first item that has changed 7089 * @param itemCount Number of items that have changed 7090 * @param payload Optional parameter, use null to identify a "full" update 7091 * 7092 * @see #notifyItemChanged(int) 7093 */ 7094 public final void notifyItemRangeChanged(int positionStart, int itemCount, 7095 @Nullable Object payload) { 7096 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); 7097 } 7098 7099 /** 7100 * Notify any registered observers that the item reflected at <code>position</code> 7101 * has been newly inserted. The item previously at <code>position</code> is now at 7102 * position <code>position + 1</code>. 7103 * 7104 * <p>This is a structural change event. Representations of other existing items in the 7105 * data set are still considered up to date and will not be rebound, though their 7106 * positions may be altered.</p> 7107 * 7108 * @param position Position of the newly inserted item in the data set 7109 * 7110 * @see #notifyItemRangeInserted(int, int) 7111 */ 7112 public final void notifyItemInserted(int position) { 7113 mObservable.notifyItemRangeInserted(position, 1); 7114 } 7115 7116 /** 7117 * Notify any registered observers that the item reflected at <code>fromPosition</code> 7118 * has been moved to <code>toPosition</code>. 7119 * 7120 * <p>This is a structural change event. Representations of other existing items in the 7121 * data set are still considered up to date and will not be rebound, though their 7122 * positions may be altered.</p> 7123 * 7124 * @param fromPosition Previous position of the item. 7125 * @param toPosition New position of the item. 7126 */ 7127 public final void notifyItemMoved(int fromPosition, int toPosition) { 7128 mObservable.notifyItemMoved(fromPosition, toPosition); 7129 } 7130 7131 /** 7132 * Notify any registered observers that the currently reflected <code>itemCount</code> 7133 * items starting at <code>positionStart</code> have been newly inserted. The items 7134 * previously located at <code>positionStart</code> and beyond can now be found starting 7135 * at position <code>positionStart + itemCount</code>. 7136 * 7137 * <p>This is a structural change event. Representations of other existing items in the 7138 * data set are still considered up to date and will not be rebound, though their positions 7139 * may be altered.</p> 7140 * 7141 * @param positionStart Position of the first item that was inserted 7142 * @param itemCount Number of items inserted 7143 * 7144 * @see #notifyItemInserted(int) 7145 */ 7146 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 7147 mObservable.notifyItemRangeInserted(positionStart, itemCount); 7148 } 7149 7150 /** 7151 * Notify any registered observers that the item previously located at <code>position</code> 7152 * has been removed from the data set. The items previously located at and after 7153 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 7154 * 7155 * <p>This is a structural change event. Representations of other existing items in the 7156 * data set are still considered up to date and will not be rebound, though their positions 7157 * may be altered.</p> 7158 * 7159 * @param position Position of the item that has now been removed 7160 * 7161 * @see #notifyItemRangeRemoved(int, int) 7162 */ 7163 public final void notifyItemRemoved(int position) { 7164 mObservable.notifyItemRangeRemoved(position, 1); 7165 } 7166 7167 /** 7168 * Notify any registered observers that the <code>itemCount</code> items previously 7169 * located at <code>positionStart</code> have been removed from the data set. The items 7170 * previously located at and after <code>positionStart + itemCount</code> may now be found 7171 * at <code>oldPosition - itemCount</code>. 7172 * 7173 * <p>This is a structural change event. Representations of other existing items in the data 7174 * set are still considered up to date and will not be rebound, though their positions 7175 * may be altered.</p> 7176 * 7177 * @param positionStart Previous position of the first item that was removed 7178 * @param itemCount Number of items removed from the data set 7179 */ 7180 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 7181 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 7182 } 7183 } 7184 7185 void dispatchChildDetached(View child) { 7186 final ViewHolder viewHolder = getChildViewHolderInt(child); 7187 onChildDetachedFromWindow(child); 7188 if (mAdapter != null && viewHolder != null) { 7189 mAdapter.onViewDetachedFromWindow(viewHolder); 7190 } 7191 if (mOnChildAttachStateListeners != null) { 7192 final int cnt = mOnChildAttachStateListeners.size(); 7193 for (int i = cnt - 1; i >= 0; i--) { 7194 mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child); 7195 } 7196 } 7197 } 7198 7199 void dispatchChildAttached(View child) { 7200 final ViewHolder viewHolder = getChildViewHolderInt(child); 7201 onChildAttachedToWindow(child); 7202 if (mAdapter != null && viewHolder != null) { 7203 mAdapter.onViewAttachedToWindow(viewHolder); 7204 } 7205 if (mOnChildAttachStateListeners != null) { 7206 final int cnt = mOnChildAttachStateListeners.size(); 7207 for (int i = cnt - 1; i >= 0; i--) { 7208 mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); 7209 } 7210 } 7211 } 7212 7213 /** 7214 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 7215 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 7216 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 7217 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 7218 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 7219 * layout managers are provided for general use. 7220 * <p/> 7221 * If the LayoutManager specifies a default constructor or one with the signature 7222 * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will 7223 * instantiate and set the LayoutManager when being inflated. Most used properties can 7224 * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case 7225 * a LayoutManager specifies both constructors, the non-default constructor will take 7226 * precedence. 7227 * 7228 */ 7229 public abstract static class LayoutManager { 7230 ChildHelper mChildHelper; 7231 RecyclerView mRecyclerView; 7232 7233 /** 7234 * The callback used for retrieving information about a RecyclerView and its children in the 7235 * horizontal direction. 7236 */ 7237 private final ViewBoundsCheck.Callback mHorizontalBoundCheckCallback = 7238 new ViewBoundsCheck.Callback() { 7239 @Override 7240 public int getChildCount() { 7241 return LayoutManager.this.getChildCount(); 7242 } 7243 7244 @Override 7245 public View getParent() { 7246 return mRecyclerView; 7247 } 7248 7249 @Override 7250 public View getChildAt(int index) { 7251 return LayoutManager.this.getChildAt(index); 7252 } 7253 7254 @Override 7255 public int getParentStart() { 7256 return LayoutManager.this.getPaddingLeft(); 7257 } 7258 7259 @Override 7260 public int getParentEnd() { 7261 return LayoutManager.this.getWidth() - LayoutManager.this.getPaddingRight(); 7262 } 7263 7264 @Override 7265 public int getChildStart(View view) { 7266 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7267 view.getLayoutParams(); 7268 return LayoutManager.this.getDecoratedLeft(view) - params.leftMargin; 7269 } 7270 7271 @Override 7272 public int getChildEnd(View view) { 7273 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7274 view.getLayoutParams(); 7275 return LayoutManager.this.getDecoratedRight(view) + params.rightMargin; 7276 } 7277 }; 7278 7279 /** 7280 * The callback used for retrieving information about a RecyclerView and its children in the 7281 * vertical direction. 7282 */ 7283 private final ViewBoundsCheck.Callback mVerticalBoundCheckCallback = 7284 new ViewBoundsCheck.Callback() { 7285 @Override 7286 public int getChildCount() { 7287 return LayoutManager.this.getChildCount(); 7288 } 7289 7290 @Override 7291 public View getParent() { 7292 return mRecyclerView; 7293 } 7294 7295 @Override 7296 public View getChildAt(int index) { 7297 return LayoutManager.this.getChildAt(index); 7298 } 7299 7300 @Override 7301 public int getParentStart() { 7302 return LayoutManager.this.getPaddingTop(); 7303 } 7304 7305 @Override 7306 public int getParentEnd() { 7307 return LayoutManager.this.getHeight() 7308 - LayoutManager.this.getPaddingBottom(); 7309 } 7310 7311 @Override 7312 public int getChildStart(View view) { 7313 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7314 view.getLayoutParams(); 7315 return LayoutManager.this.getDecoratedTop(view) - params.topMargin; 7316 } 7317 7318 @Override 7319 public int getChildEnd(View view) { 7320 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7321 view.getLayoutParams(); 7322 return LayoutManager.this.getDecoratedBottom(view) + params.bottomMargin; 7323 } 7324 }; 7325 7326 /** 7327 * Utility objects used to check the boundaries of children against their parent 7328 * RecyclerView. 7329 * @see #isViewPartiallyVisible(View, boolean, boolean), 7330 * {@link LinearLayoutManager#findOneVisibleChild(int, int, boolean, boolean)}, 7331 * and {@link LinearLayoutManager#findOnePartiallyOrCompletelyInvisibleChild(int, int)}. 7332 */ 7333 ViewBoundsCheck mHorizontalBoundCheck = new ViewBoundsCheck(mHorizontalBoundCheckCallback); 7334 ViewBoundsCheck mVerticalBoundCheck = new ViewBoundsCheck(mVerticalBoundCheckCallback); 7335 7336 @Nullable 7337 SmoothScroller mSmoothScroller; 7338 7339 boolean mRequestedSimpleAnimations = false; 7340 7341 boolean mIsAttachedToWindow = false; 7342 7343 /** 7344 * This field is only set via the deprecated {@link #setAutoMeasureEnabled(boolean)} and is 7345 * only accessed via {@link #isAutoMeasureEnabled()} for backwards compatability reasons. 7346 */ 7347 boolean mAutoMeasure = false; 7348 7349 /** 7350 * LayoutManager has its own more strict measurement cache to avoid re-measuring a child 7351 * if the space that will be given to it is already larger than what it has measured before. 7352 */ 7353 private boolean mMeasurementCacheEnabled = true; 7354 7355 private boolean mItemPrefetchEnabled = true; 7356 7357 /** 7358 * Written by {@link GapWorker} when prefetches occur to track largest number of view ever 7359 * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or 7360 * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. 7361 * 7362 * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, 7363 * will be reset upon layout to prevent initial prefetches (often large, since they're 7364 * proportional to expected child count) from expanding cache permanently. 7365 */ 7366 int mPrefetchMaxCountObserved; 7367 7368 /** 7369 * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. 7370 */ 7371 boolean mPrefetchMaxObservedInInitialPrefetch; 7372 7373 /** 7374 * These measure specs might be the measure specs that were passed into RecyclerView's 7375 * onMeasure method OR fake measure specs created by the RecyclerView. 7376 * For example, when a layout is run, RecyclerView always sets these specs to be 7377 * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. 7378 * <p> 7379 * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the 7380 * API level and sets the size to 0 pre-M to avoid any issue that might be caused by 7381 * corrupt values. Older platforms have no responsibility to provide a size if they set 7382 * mode to unspecified. 7383 */ 7384 private int mWidthMode, mHeightMode; 7385 private int mWidth, mHeight; 7386 7387 7388 /** 7389 * Interface for LayoutManagers to request items to be prefetched, based on position, with 7390 * specified distance from viewport, which indicates priority. 7391 * 7392 * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 7393 * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 7394 */ 7395 public interface LayoutPrefetchRegistry { 7396 /** 7397 * Requests an an item to be prefetched, based on position, with a specified distance, 7398 * indicating priority. 7399 * 7400 * @param layoutPosition Position of the item to prefetch. 7401 * @param pixelDistance Distance from the current viewport to the bounds of the item, 7402 * must be non-negative. 7403 */ 7404 void addPosition(int layoutPosition, int pixelDistance); 7405 } 7406 7407 void setRecyclerView(RecyclerView recyclerView) { 7408 if (recyclerView == null) { 7409 mRecyclerView = null; 7410 mChildHelper = null; 7411 mWidth = 0; 7412 mHeight = 0; 7413 } else { 7414 mRecyclerView = recyclerView; 7415 mChildHelper = recyclerView.mChildHelper; 7416 mWidth = recyclerView.getWidth(); 7417 mHeight = recyclerView.getHeight(); 7418 } 7419 mWidthMode = MeasureSpec.EXACTLY; 7420 mHeightMode = MeasureSpec.EXACTLY; 7421 } 7422 7423 void setMeasureSpecs(int wSpec, int hSpec) { 7424 mWidth = MeasureSpec.getSize(wSpec); 7425 mWidthMode = MeasureSpec.getMode(wSpec); 7426 if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 7427 mWidth = 0; 7428 } 7429 7430 mHeight = MeasureSpec.getSize(hSpec); 7431 mHeightMode = MeasureSpec.getMode(hSpec); 7432 if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 7433 mHeight = 0; 7434 } 7435 } 7436 7437 /** 7438 * Called after a layout is calculated during a measure pass when using auto-measure. 7439 * <p> 7440 * It simply traverses all children to calculate a bounding box then calls 7441 * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method 7442 * if they need to handle the bounding box differently. 7443 * <p> 7444 * For example, GridLayoutManager override that method to ensure that even if a column is 7445 * empty, the GridLayoutManager still measures wide enough to include it. 7446 * 7447 * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure 7448 * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure 7449 */ 7450 void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { 7451 final int count = getChildCount(); 7452 if (count == 0) { 7453 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 7454 return; 7455 } 7456 int minX = Integer.MAX_VALUE; 7457 int minY = Integer.MAX_VALUE; 7458 int maxX = Integer.MIN_VALUE; 7459 int maxY = Integer.MIN_VALUE; 7460 7461 for (int i = 0; i < count; i++) { 7462 View child = getChildAt(i); 7463 final Rect bounds = mRecyclerView.mTempRect; 7464 getDecoratedBoundsWithMargins(child, bounds); 7465 if (bounds.left < minX) { 7466 minX = bounds.left; 7467 } 7468 if (bounds.right > maxX) { 7469 maxX = bounds.right; 7470 } 7471 if (bounds.top < minY) { 7472 minY = bounds.top; 7473 } 7474 if (bounds.bottom > maxY) { 7475 maxY = bounds.bottom; 7476 } 7477 } 7478 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); 7479 setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); 7480 } 7481 7482 /** 7483 * Sets the measured dimensions from the given bounding box of the children and the 7484 * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is 7485 * only called if a LayoutManager returns <code>true</code> from 7486 * {@link #isAutoMeasureEnabled()} and it is called after the RecyclerView calls 7487 * {@link LayoutManager#onLayoutChildren(Recycler, State)} in the execution of 7488 * {@link RecyclerView#onMeasure(int, int)}. 7489 * <p> 7490 * This method must call {@link #setMeasuredDimension(int, int)}. 7491 * <p> 7492 * The default implementation adds the RecyclerView's padding to the given bounding box 7493 * then caps the value to be within the given measurement specs. 7494 * 7495 * @param childrenBounds The bounding box of all children 7496 * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. 7497 * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. 7498 * 7499 * @see #isAutoMeasureEnabled() 7500 * @see #setMeasuredDimension(int, int) 7501 */ 7502 public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { 7503 int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); 7504 int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); 7505 int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); 7506 int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); 7507 setMeasuredDimension(width, height); 7508 } 7509 7510 /** 7511 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView 7512 */ 7513 public void requestLayout() { 7514 if (mRecyclerView != null) { 7515 mRecyclerView.requestLayout(); 7516 } 7517 } 7518 7519 /** 7520 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 7521 * {@link IllegalStateException} if it <b>is not</b>. 7522 * 7523 * @param message The message for the exception. Can be null. 7524 * @see #assertNotInLayoutOrScroll(String) 7525 */ 7526 public void assertInLayoutOrScroll(String message) { 7527 if (mRecyclerView != null) { 7528 mRecyclerView.assertInLayoutOrScroll(message); 7529 } 7530 } 7531 7532 /** 7533 * Chooses a size from the given specs and parameters that is closest to the desired size 7534 * and also complies with the spec. 7535 * 7536 * @param spec The measureSpec 7537 * @param desired The preferred measurement 7538 * @param min The minimum value 7539 * 7540 * @return A size that fits to the given specs 7541 */ 7542 public static int chooseSize(int spec, int desired, int min) { 7543 final int mode = View.MeasureSpec.getMode(spec); 7544 final int size = View.MeasureSpec.getSize(spec); 7545 switch (mode) { 7546 case View.MeasureSpec.EXACTLY: 7547 return size; 7548 case View.MeasureSpec.AT_MOST: 7549 return Math.min(size, Math.max(desired, min)); 7550 case View.MeasureSpec.UNSPECIFIED: 7551 default: 7552 return Math.max(desired, min); 7553 } 7554 } 7555 7556 /** 7557 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 7558 * {@link IllegalStateException} if it <b>is</b>. 7559 * 7560 * @param message The message for the exception. Can be null. 7561 * @see #assertInLayoutOrScroll(String) 7562 */ 7563 public void assertNotInLayoutOrScroll(String message) { 7564 if (mRecyclerView != null) { 7565 mRecyclerView.assertNotInLayoutOrScroll(message); 7566 } 7567 } 7568 7569 /** 7570 * Defines whether the measuring pass of layout should use the AutoMeasure mechanism of 7571 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 7572 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7573 * 7574 * @param enabled <code>True</code> if layout measurement should be done by the 7575 * RecyclerView, <code>false</code> if it should be done by this 7576 * LayoutManager. 7577 * 7578 * @see #isAutoMeasureEnabled() 7579 * 7580 * @deprecated Implementors of LayoutManager should define whether or not it uses 7581 * AutoMeasure by overriding {@link #isAutoMeasureEnabled()}. 7582 */ 7583 @Deprecated 7584 public void setAutoMeasureEnabled(boolean enabled) { 7585 mAutoMeasure = enabled; 7586 } 7587 7588 /** 7589 * Returns whether the measuring pass of layout should use the AutoMeasure mechanism of 7590 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 7591 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7592 * <p> 7593 * This method returns false by default (it actually returns the value passed to the 7594 * deprecated {@link #setAutoMeasureEnabled(boolean)}) and should be overridden to return 7595 * true if a LayoutManager wants to be auto measured by the RecyclerView. 7596 * <p> 7597 * If this method is overridden to return true, 7598 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} should not be overridden. 7599 * <p> 7600 * AutoMeasure is a RecyclerView mechanism that handles the measuring pass of layout in a 7601 * simple and contract satisfying way, including the wrapping of children laid out by 7602 * LayoutManager. Simply put, it handles wrapping children by calling 7603 * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a call to 7604 * {@link RecyclerView#onMeasure(int, int)}, and then calculating desired dimensions based 7605 * on children's dimensions and positions. It does this while supporting all existing 7606 * animation capabilities of the RecyclerView. 7607 * <p> 7608 * More specifically: 7609 * <ol> 7610 * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided measure 7611 * specs both have a mode of {@link View.MeasureSpec#EXACTLY}, RecyclerView will set its 7612 * measured dimensions accordingly and return, allowing layout to continue as normal 7613 * (Actually, RecyclerView will call 7614 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} for backwards compatibility 7615 * reasons but it should not be overridden if AutoMeasure is being used).</li> 7616 * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the 7617 * layout process. It will first process all pending Adapter updates and 7618 * then decide whether to run a predictive layout. If it decides to do so, it will first 7619 * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to 7620 * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still 7621 * return the width and height of the RecyclerView as of the last layout calculation. 7622 * <p> 7623 * After handling the predictive case, RecyclerView will call 7624 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 7625 * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can 7626 * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, 7627 * {@link #getWidth()} and {@link #getWidthMode()}.</li> 7628 * <li>After the layout calculation, RecyclerView sets the measured width & height by 7629 * calculating the bounding box for the children (+ RecyclerView's padding). The 7630 * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose 7631 * different values. For instance, GridLayoutManager overrides this value to handle the case 7632 * where if it is vertical and has 3 columns but only 2 items, it should still measure its 7633 * width to fit 3 items, not 2.</li> 7634 * <li>Any following calls to {@link RecyclerView#onMeasure(int, int)} will run 7635 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 7636 * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will 7637 * take care of which views are actually added / removed / moved / changed for animations so 7638 * that the LayoutManager should not worry about them and handle each 7639 * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.</li> 7640 * <li>When measure is complete and RecyclerView's 7641 * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks 7642 * whether it already did layout calculations during the measure pass and if so, it re-uses 7643 * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} 7644 * if the last measure spec was different from the final dimensions or adapter contents 7645 * have changed between the measure call and the layout call.</li> 7646 * <li>Finally, animations are calculated and run as usual.</li> 7647 * </ol> 7648 * 7649 * @return <code>True</code> if the measuring pass of layout should use the AutoMeasure 7650 * mechanism of {@link RecyclerView} or <code>False</code> if it should be done by the 7651 * LayoutManager's implementation of 7652 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7653 * 7654 * @see #setMeasuredDimension(Rect, int, int) 7655 * @see #onMeasure(Recycler, State, int, int) 7656 */ 7657 public boolean isAutoMeasureEnabled() { 7658 return mAutoMeasure; 7659 } 7660 7661 /** 7662 * Returns whether this LayoutManager supports "predictive item animations". 7663 * <p> 7664 * "Predictive item animations" are automatically created animations that show 7665 * where items came from, and where they are going to, as items are added, removed, 7666 * or moved within a layout. 7667 * <p> 7668 * A LayoutManager wishing to support predictive item animations must override this 7669 * method to return true (the default implementation returns false) and must obey certain 7670 * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}. 7671 * <p> 7672 * Whether item animations actually occur in a RecyclerView is actually determined by both 7673 * the return value from this method and the 7674 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the 7675 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this 7676 * method returns false, then only "simple item animations" will be enabled in the 7677 * RecyclerView, in which views whose position are changing are simply faded in/out. If the 7678 * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive 7679 * item animations will be enabled in the RecyclerView. 7680 * 7681 * @return true if this LayoutManager supports predictive item animations, false otherwise. 7682 */ 7683 public boolean supportsPredictiveItemAnimations() { 7684 return false; 7685 } 7686 7687 /** 7688 * Sets whether the LayoutManager should be queried for views outside of 7689 * its viewport while the UI thread is idle between frames. 7690 * 7691 * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between 7692 * view system traversals on devices running API 21 or greater. Default value is true.</p> 7693 * 7694 * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame 7695 * to RenderThread and the starting up its next frame at the next VSync pulse. By 7696 * prefetching out of window views in this time period, delays from inflation and view 7697 * binding are much less likely to cause jank and stuttering during scrolls and flings.</p> 7698 * 7699 * <p>While prefetch is enabled, it will have the side effect of expanding the effective 7700 * size of the View cache to hold prefetched views.</p> 7701 * 7702 * @param enabled <code>True</code> if items should be prefetched in between traversals. 7703 * 7704 * @see #isItemPrefetchEnabled() 7705 */ 7706 public final void setItemPrefetchEnabled(boolean enabled) { 7707 if (enabled != mItemPrefetchEnabled) { 7708 mItemPrefetchEnabled = enabled; 7709 mPrefetchMaxCountObserved = 0; 7710 if (mRecyclerView != null) { 7711 mRecyclerView.mRecycler.updateViewCacheSize(); 7712 } 7713 } 7714 } 7715 7716 /** 7717 * Sets whether the LayoutManager should be queried for views outside of 7718 * its viewport while the UI thread is idle between frames. 7719 * 7720 * @see #setItemPrefetchEnabled(boolean) 7721 * 7722 * @return true if item prefetch is enabled, false otherwise 7723 */ 7724 public final boolean isItemPrefetchEnabled() { 7725 return mItemPrefetchEnabled; 7726 } 7727 7728 /** 7729 * Gather all positions from the LayoutManager to be prefetched, given specified momentum. 7730 * 7731 * <p>If item prefetch is enabled, this method is called in between traversals to gather 7732 * which positions the LayoutManager will soon need, given upcoming movement in subsequent 7733 * traversals.</p> 7734 * 7735 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 7736 * each item to be prepared, and these positions will have their ViewHolders created and 7737 * bound, if there is sufficient time available, in advance of being needed by a 7738 * scroll or layout.</p> 7739 * 7740 * @param dx X movement component. 7741 * @param dy Y movement component. 7742 * @param state State of RecyclerView 7743 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 7744 * 7745 * @see #isItemPrefetchEnabled() 7746 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 7747 */ 7748 public void collectAdjacentPrefetchPositions(int dx, int dy, State state, 7749 LayoutPrefetchRegistry layoutPrefetchRegistry) {} 7750 7751 /** 7752 * Gather all positions from the LayoutManager to be prefetched in preperation for its 7753 * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. 7754 * 7755 * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p> 7756 * 7757 * <p>If item prefetch is enabled for this LayoutManager, as well in another containing 7758 * LayoutManager, this method is called in between draw traversals to gather 7759 * which positions this LayoutManager will first need, once it appears on the screen.</p> 7760 * 7761 * <p>For example, if this LayoutManager represents a horizontally scrolling list within a 7762 * vertically scrolling LayoutManager, this method would be called when the horizontal list 7763 * is about to come onscreen.</p> 7764 * 7765 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 7766 * each item to be prepared, and these positions will have their ViewHolders created and 7767 * bound, if there is sufficient time available, in advance of being needed by a 7768 * scroll or layout.</p> 7769 * 7770 * @param adapterItemCount number of items in the associated adapter. 7771 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 7772 * 7773 * @see #isItemPrefetchEnabled() 7774 * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 7775 */ 7776 public void collectInitialPrefetchPositions(int adapterItemCount, 7777 LayoutPrefetchRegistry layoutPrefetchRegistry) {} 7778 7779 void dispatchAttachedToWindow(RecyclerView view) { 7780 mIsAttachedToWindow = true; 7781 onAttachedToWindow(view); 7782 } 7783 7784 void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { 7785 mIsAttachedToWindow = false; 7786 onDetachedFromWindow(view, recycler); 7787 } 7788 7789 /** 7790 * Returns whether LayoutManager is currently attached to a RecyclerView which is attached 7791 * to a window. 7792 * 7793 * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView 7794 * is attached to window. 7795 */ 7796 public boolean isAttachedToWindow() { 7797 return mIsAttachedToWindow; 7798 } 7799 7800 /** 7801 * Causes the Runnable to execute on the next animation time step. 7802 * The runnable will be run on the user interface thread. 7803 * <p> 7804 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 7805 * 7806 * @param action The Runnable that will be executed. 7807 * 7808 * @see #removeCallbacks 7809 */ 7810 public void postOnAnimation(Runnable action) { 7811 if (mRecyclerView != null) { 7812 ViewCompat.postOnAnimation(mRecyclerView, action); 7813 } 7814 } 7815 7816 /** 7817 * Removes the specified Runnable from the message queue. 7818 * <p> 7819 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 7820 * 7821 * @param action The Runnable to remove from the message handling queue 7822 * 7823 * @return true if RecyclerView could ask the Handler to remove the Runnable, 7824 * false otherwise. When the returned value is true, the Runnable 7825 * may or may not have been actually removed from the message queue 7826 * (for instance, if the Runnable was not in the queue already.) 7827 * 7828 * @see #postOnAnimation 7829 */ 7830 public boolean removeCallbacks(Runnable action) { 7831 if (mRecyclerView != null) { 7832 return mRecyclerView.removeCallbacks(action); 7833 } 7834 return false; 7835 } 7836 /** 7837 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 7838 * is attached to a window. 7839 * <p> 7840 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 7841 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 7842 * not requested on the RecyclerView while it was detached. 7843 * <p> 7844 * Subclass implementations should always call through to the superclass implementation. 7845 * 7846 * @param view The RecyclerView this LayoutManager is bound to 7847 * 7848 * @see #onDetachedFromWindow(RecyclerView, Recycler) 7849 */ 7850 @CallSuper 7851 public void onAttachedToWindow(RecyclerView view) { 7852 } 7853 7854 /** 7855 * @deprecated 7856 * override {@link #onDetachedFromWindow(RecyclerView, Recycler)} 7857 */ 7858 @Deprecated 7859 public void onDetachedFromWindow(RecyclerView view) { 7860 7861 } 7862 7863 /** 7864 * Called when this LayoutManager is detached from its parent RecyclerView or when 7865 * its parent RecyclerView is detached from its window. 7866 * <p> 7867 * LayoutManager should clear all of its View references as another LayoutManager might be 7868 * assigned to the RecyclerView. 7869 * <p> 7870 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 7871 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 7872 * not requested on the RecyclerView while it was detached. 7873 * <p> 7874 * If your LayoutManager has View references that it cleans in on-detach, it should also 7875 * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when 7876 * RecyclerView is re-attached. 7877 * <p> 7878 * Subclass implementations should always call through to the superclass implementation. 7879 * 7880 * @param view The RecyclerView this LayoutManager is bound to 7881 * @param recycler The recycler to use if you prefer to recycle your children instead of 7882 * keeping them around. 7883 * 7884 * @see #onAttachedToWindow(RecyclerView) 7885 */ 7886 @CallSuper 7887 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { 7888 onDetachedFromWindow(view); 7889 } 7890 7891 /** 7892 * Check if the RecyclerView is configured to clip child views to its padding. 7893 * 7894 * @return true if this RecyclerView clips children to its padding, false otherwise 7895 */ 7896 public boolean getClipToPadding() { 7897 return mRecyclerView != null && mRecyclerView.mClipToPadding; 7898 } 7899 7900 /** 7901 * Lay out all relevant child views from the given adapter. 7902 * 7903 * The LayoutManager is in charge of the behavior of item animations. By default, 7904 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple 7905 * item animations are enabled. This means that add/remove operations on the 7906 * adapter will result in animations to add new or appearing items, removed or 7907 * disappearing items, and moved items. If a LayoutManager returns false from 7908 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a 7909 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the 7910 * RecyclerView will have enough information to run those animations in a simple 7911 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will 7912 * simply fade views in and out, whether they are actually added/removed or whether 7913 * they are moved on or off the screen due to other add/remove operations. 7914 * 7915 * <p>A LayoutManager wanting a better item animation experience, where items can be 7916 * animated onto and off of the screen according to where the items exist when they 7917 * are not on screen, then the LayoutManager should return true from 7918 * {@link #supportsPredictiveItemAnimations()} and add additional logic to 7919 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations 7920 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; 7921 * once as a "pre" layout step to determine where items would have been prior to 7922 * a real layout, and again to do the "real" layout. In the pre-layout phase, 7923 * items will remember their pre-layout positions to allow them to be laid out 7924 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will 7925 * be returned from the scrap to help determine correct placement of other items. 7926 * These removed items should not be added to the child list, but should be used 7927 * to help calculate correct positioning of other views, including views that 7928 * were not previously onscreen (referred to as APPEARING views), but whose 7929 * pre-layout offscreen position can be determined given the extra 7930 * information about the pre-layout removed views.</p> 7931 * 7932 * <p>The second layout pass is the real layout in which only non-removed views 7933 * will be used. The only additional requirement during this pass is, if 7934 * {@link #supportsPredictiveItemAnimations()} returns true, to note which 7935 * views exist in the child list prior to layout and which are not there after 7936 * layout (referred to as DISAPPEARING views), and to position/layout those views 7937 * appropriately, without regard to the actual bounds of the RecyclerView. This allows 7938 * the animation system to know the location to which to animate these disappearing 7939 * views.</p> 7940 * 7941 * <p>The default LayoutManager implementations for RecyclerView handle all of these 7942 * requirements for animations already. Clients of RecyclerView can either use one 7943 * of these layout managers directly or look at their implementations of 7944 * onLayoutChildren() to see how they account for the APPEARING and 7945 * DISAPPEARING views.</p> 7946 * 7947 * @param recycler Recycler to use for fetching potentially cached views for a 7948 * position 7949 * @param state Transient state of RecyclerView 7950 */ 7951 public void onLayoutChildren(Recycler recycler, State state) { 7952 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); 7953 } 7954 7955 /** 7956 * Called after a full layout calculation is finished. The layout calculation may include 7957 * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or 7958 * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. 7959 * This method will be called at the end of {@link View#layout(int, int, int, int)} call. 7960 * <p> 7961 * This is a good place for the LayoutManager to do some cleanup like pending scroll 7962 * position, saved state etc. 7963 * 7964 * @param state Transient state of RecyclerView 7965 */ 7966 public void onLayoutCompleted(State state) { 7967 } 7968 7969 /** 7970 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 7971 * 7972 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 7973 * to store extra information specific to the layout. Client code should subclass 7974 * {@link RecyclerView.LayoutParams} for this purpose.</p> 7975 * 7976 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 7977 * you must also override 7978 * {@link #checkLayoutParams(LayoutParams)}, 7979 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 7980 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 7981 * 7982 * @return A new LayoutParams for a child view 7983 */ 7984 public abstract LayoutParams generateDefaultLayoutParams(); 7985 7986 /** 7987 * Determines the validity of the supplied LayoutParams object. 7988 * 7989 * <p>This should check to make sure that the object is of the correct type 7990 * and all values are within acceptable ranges. The default implementation 7991 * returns <code>true</code> for non-null params.</p> 7992 * 7993 * @param lp LayoutParams object to check 7994 * @return true if this LayoutParams object is valid, false otherwise 7995 */ 7996 public boolean checkLayoutParams(LayoutParams lp) { 7997 return lp != null; 7998 } 7999 8000 /** 8001 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 8002 * values from the supplied LayoutParams object if possible. 8003 * 8004 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 8005 * you must also override 8006 * {@link #checkLayoutParams(LayoutParams)}, 8007 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 8008 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 8009 * 8010 * @param lp Source LayoutParams object to copy values from 8011 * @return a new LayoutParams object 8012 */ 8013 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 8014 if (lp instanceof LayoutParams) { 8015 return new LayoutParams((LayoutParams) lp); 8016 } else if (lp instanceof MarginLayoutParams) { 8017 return new LayoutParams((MarginLayoutParams) lp); 8018 } else { 8019 return new LayoutParams(lp); 8020 } 8021 } 8022 8023 /** 8024 * Create a LayoutParams object suitable for this LayoutManager from 8025 * an inflated layout resource. 8026 * 8027 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 8028 * you must also override 8029 * {@link #checkLayoutParams(LayoutParams)}, 8030 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 8031 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 8032 * 8033 * @param c Context for obtaining styled attributes 8034 * @param attrs AttributeSet describing the supplied arguments 8035 * @return a new LayoutParams object 8036 */ 8037 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 8038 return new LayoutParams(c, attrs); 8039 } 8040 8041 /** 8042 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 8043 * The default implementation does nothing and returns 0. 8044 * 8045 * @param dx distance to scroll by in pixels. X increases as scroll position 8046 * approaches the right. 8047 * @param recycler Recycler to use for fetching potentially cached views for a 8048 * position 8049 * @param state Transient state of RecyclerView 8050 * @return The actual distance scrolled. The return value will be negative if dx was 8051 * negative and scrolling proceeeded in that direction. 8052 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 8053 */ 8054 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { 8055 return 0; 8056 } 8057 8058 /** 8059 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 8060 * The default implementation does nothing and returns 0. 8061 * 8062 * @param dy distance to scroll in pixels. Y increases as scroll position 8063 * approaches the bottom. 8064 * @param recycler Recycler to use for fetching potentially cached views for a 8065 * position 8066 * @param state Transient state of RecyclerView 8067 * @return The actual distance scrolled. The return value will be negative if dy was 8068 * negative and scrolling proceeeded in that direction. 8069 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 8070 */ 8071 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { 8072 return 0; 8073 } 8074 8075 /** 8076 * Query if horizontal scrolling is currently supported. The default implementation 8077 * returns false. 8078 * 8079 * @return True if this LayoutManager can scroll the current contents horizontally 8080 */ 8081 public boolean canScrollHorizontally() { 8082 return false; 8083 } 8084 8085 /** 8086 * Query if vertical scrolling is currently supported. The default implementation 8087 * returns false. 8088 * 8089 * @return True if this LayoutManager can scroll the current contents vertically 8090 */ 8091 public boolean canScrollVertically() { 8092 return false; 8093 } 8094 8095 /** 8096 * Scroll to the specified adapter position. 8097 * 8098 * Actual position of the item on the screen depends on the LayoutManager implementation. 8099 * @param position Scroll to this adapter position. 8100 */ 8101 public void scrollToPosition(int position) { 8102 if (DEBUG) { 8103 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); 8104 } 8105 } 8106 8107 /** 8108 * <p>Smooth scroll to the specified adapter position.</p> 8109 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} 8110 * instance and call {@link #startSmoothScroll(SmoothScroller)}. 8111 * </p> 8112 * @param recyclerView The RecyclerView to which this layout manager is attached 8113 * @param state Current State of RecyclerView 8114 * @param position Scroll to this adapter position. 8115 */ 8116 public void smoothScrollToPosition(RecyclerView recyclerView, State state, 8117 int position) { 8118 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); 8119 } 8120 8121 /** 8122 * <p>Starts a smooth scroll using the provided SmoothScroller.</p> 8123 * <p>Calling this method will cancel any previous smooth scroll request.</p> 8124 * @param smoothScroller Instance which defines how smooth scroll should be animated 8125 */ 8126 public void startSmoothScroll(SmoothScroller smoothScroller) { 8127 if (mSmoothScroller != null && smoothScroller != mSmoothScroller 8128 && mSmoothScroller.isRunning()) { 8129 mSmoothScroller.stop(); 8130 } 8131 mSmoothScroller = smoothScroller; 8132 mSmoothScroller.start(mRecyclerView, this); 8133 } 8134 8135 /** 8136 * @return true if RecyclerView is currently in the state of smooth scrolling. 8137 */ 8138 public boolean isSmoothScrolling() { 8139 return mSmoothScroller != null && mSmoothScroller.isRunning(); 8140 } 8141 8142 8143 /** 8144 * Returns the resolved layout direction for this RecyclerView. 8145 * 8146 * @return {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout 8147 * direction is RTL or returns 8148 * {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction 8149 * is not RTL. 8150 */ 8151 public int getLayoutDirection() { 8152 return ViewCompat.getLayoutDirection(mRecyclerView); 8153 } 8154 8155 /** 8156 * Ends all animations on the view created by the {@link ItemAnimator}. 8157 * 8158 * @param view The View for which the animations should be ended. 8159 * @see RecyclerView.ItemAnimator#endAnimations() 8160 */ 8161 public void endAnimation(View view) { 8162 if (mRecyclerView.mItemAnimator != null) { 8163 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); 8164 } 8165 } 8166 8167 /** 8168 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 8169 * to the layout that is known to be going away, either because it has been 8170 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 8171 * visible portion of the container but is being laid out in order to inform RecyclerView 8172 * in how to animate the item out of view. 8173 * <p> 8174 * Views added via this method are going to be invisible to LayoutManager after the 8175 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 8176 * or won't be included in {@link #getChildCount()} method. 8177 * 8178 * @param child View to add and then remove with animation. 8179 */ 8180 public void addDisappearingView(View child) { 8181 addDisappearingView(child, -1); 8182 } 8183 8184 /** 8185 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 8186 * to the layout that is known to be going away, either because it has been 8187 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 8188 * visible portion of the container but is being laid out in order to inform RecyclerView 8189 * in how to animate the item out of view. 8190 * <p> 8191 * Views added via this method are going to be invisible to LayoutManager after the 8192 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 8193 * or won't be included in {@link #getChildCount()} method. 8194 * 8195 * @param child View to add and then remove with animation. 8196 * @param index Index of the view. 8197 */ 8198 public void addDisappearingView(View child, int index) { 8199 addViewInt(child, index, true); 8200 } 8201 8202 /** 8203 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 8204 * use this method to add views obtained from a {@link Recycler} using 8205 * {@link Recycler#getViewForPosition(int)}. 8206 * 8207 * @param child View to add 8208 */ 8209 public void addView(View child) { 8210 addView(child, -1); 8211 } 8212 8213 /** 8214 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 8215 * use this method to add views obtained from a {@link Recycler} using 8216 * {@link Recycler#getViewForPosition(int)}. 8217 * 8218 * @param child View to add 8219 * @param index Index to add child at 8220 */ 8221 public void addView(View child, int index) { 8222 addViewInt(child, index, false); 8223 } 8224 8225 private void addViewInt(View child, int index, boolean disappearing) { 8226 final ViewHolder holder = getChildViewHolderInt(child); 8227 if (disappearing || holder.isRemoved()) { 8228 // these views will be hidden at the end of the layout pass. 8229 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); 8230 } else { 8231 // This may look like unnecessary but may happen if layout manager supports 8232 // predictive layouts and adapter removed then re-added the same item. 8233 // In this case, added version will be visible in the post layout (because add is 8234 // deferred) but RV will still bind it to the same View. 8235 // So if a View re-appears in post layout pass, remove it from disappearing list. 8236 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); 8237 } 8238 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 8239 if (holder.wasReturnedFromScrap() || holder.isScrap()) { 8240 if (holder.isScrap()) { 8241 holder.unScrap(); 8242 } else { 8243 holder.clearReturnedFromScrapFlag(); 8244 } 8245 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); 8246 if (DISPATCH_TEMP_DETACH) { 8247 ViewCompat.dispatchFinishTemporaryDetach(child); 8248 } 8249 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child 8250 // ensure in correct position 8251 int currentIndex = mChildHelper.indexOfChild(child); 8252 if (index == -1) { 8253 index = mChildHelper.getChildCount(); 8254 } 8255 if (currentIndex == -1) { 8256 throw new IllegalStateException("Added View has RecyclerView as parent but" 8257 + " view is not a real child. Unfiltered index:" 8258 + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel()); 8259 } 8260 if (currentIndex != index) { 8261 mRecyclerView.mLayout.moveView(currentIndex, index); 8262 } 8263 } else { 8264 mChildHelper.addView(child, index, false); 8265 lp.mInsetsDirty = true; 8266 if (mSmoothScroller != null && mSmoothScroller.isRunning()) { 8267 mSmoothScroller.onChildAttachedToWindow(child); 8268 } 8269 } 8270 if (lp.mPendingInvalidate) { 8271 if (DEBUG) { 8272 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); 8273 } 8274 holder.itemView.invalidate(); 8275 lp.mPendingInvalidate = false; 8276 } 8277 } 8278 8279 /** 8280 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 8281 * use this method to completely remove a child view that is no longer needed. 8282 * LayoutManagers should strongly consider recycling removed views using 8283 * {@link Recycler#recycleView(android.view.View)}. 8284 * 8285 * @param child View to remove 8286 */ 8287 public void removeView(View child) { 8288 mChildHelper.removeView(child); 8289 } 8290 8291 /** 8292 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 8293 * use this method to completely remove a child view that is no longer needed. 8294 * LayoutManagers should strongly consider recycling removed views using 8295 * {@link Recycler#recycleView(android.view.View)}. 8296 * 8297 * @param index Index of the child view to remove 8298 */ 8299 public void removeViewAt(int index) { 8300 final View child = getChildAt(index); 8301 if (child != null) { 8302 mChildHelper.removeViewAt(index); 8303 } 8304 } 8305 8306 /** 8307 * Remove all views from the currently attached RecyclerView. This will not recycle 8308 * any of the affected views; the LayoutManager is responsible for doing so if desired. 8309 */ 8310 public void removeAllViews() { 8311 // Only remove non-animating views 8312 final int childCount = getChildCount(); 8313 for (int i = childCount - 1; i >= 0; i--) { 8314 mChildHelper.removeViewAt(i); 8315 } 8316 } 8317 8318 /** 8319 * Returns offset of the RecyclerView's text baseline from the its top boundary. 8320 * 8321 * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if 8322 * there is no baseline. 8323 */ 8324 public int getBaseline() { 8325 return -1; 8326 } 8327 8328 /** 8329 * Returns the adapter position of the item represented by the given View. This does not 8330 * contain any adapter changes that might have happened after the last layout. 8331 * 8332 * @param view The view to query 8333 * @return The adapter position of the item which is rendered by this View. 8334 */ 8335 public int getPosition(View view) { 8336 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 8337 } 8338 8339 /** 8340 * Returns the View type defined by the adapter. 8341 * 8342 * @param view The view to query 8343 * @return The type of the view assigned by the adapter. 8344 */ 8345 public int getItemViewType(View view) { 8346 return getChildViewHolderInt(view).getItemViewType(); 8347 } 8348 8349 /** 8350 * Traverses the ancestors of the given view and returns the item view that contains it 8351 * and also a direct child of the LayoutManager. 8352 * <p> 8353 * Note that this method may return null if the view is a child of the RecyclerView but 8354 * not a child of the LayoutManager (e.g. running a disappear animation). 8355 * 8356 * @param view The view that is a descendant of the LayoutManager. 8357 * 8358 * @return The direct child of the LayoutManager which contains the given view or null if 8359 * the provided view is not a descendant of this LayoutManager. 8360 * 8361 * @see RecyclerView#getChildViewHolder(View) 8362 * @see RecyclerView#findContainingViewHolder(View) 8363 */ 8364 @Nullable 8365 public View findContainingItemView(View view) { 8366 if (mRecyclerView == null) { 8367 return null; 8368 } 8369 View found = mRecyclerView.findContainingItemView(view); 8370 if (found == null) { 8371 return null; 8372 } 8373 if (mChildHelper.isHidden(found)) { 8374 return null; 8375 } 8376 return found; 8377 } 8378 8379 /** 8380 * Finds the view which represents the given adapter position. 8381 * <p> 8382 * This method traverses each child since it has no information about child order. 8383 * Override this method to improve performance if your LayoutManager keeps data about 8384 * child views. 8385 * <p> 8386 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. 8387 * 8388 * @param position Position of the item in adapter 8389 * @return The child view that represents the given position or null if the position is not 8390 * laid out 8391 */ 8392 public View findViewByPosition(int position) { 8393 final int childCount = getChildCount(); 8394 for (int i = 0; i < childCount; i++) { 8395 View child = getChildAt(i); 8396 ViewHolder vh = getChildViewHolderInt(child); 8397 if (vh == null) { 8398 continue; 8399 } 8400 if (vh.getLayoutPosition() == position && !vh.shouldIgnore() 8401 && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { 8402 return child; 8403 } 8404 } 8405 return null; 8406 } 8407 8408 /** 8409 * Temporarily detach a child view. 8410 * 8411 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 8412 * views currently attached to the RecyclerView. Generally LayoutManager implementations 8413 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 8414 * so that the detached view may be rebound and reused.</p> 8415 * 8416 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 8417 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 8418 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 8419 * before the LayoutManager entry point method called by RecyclerView returns.</p> 8420 * 8421 * @param child Child to detach 8422 */ 8423 public void detachView(View child) { 8424 final int ind = mChildHelper.indexOfChild(child); 8425 if (ind >= 0) { 8426 detachViewInternal(ind, child); 8427 } 8428 } 8429 8430 /** 8431 * Temporarily detach a child view. 8432 * 8433 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 8434 * views currently attached to the RecyclerView. Generally LayoutManager implementations 8435 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 8436 * so that the detached view may be rebound and reused.</p> 8437 * 8438 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 8439 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 8440 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 8441 * before the LayoutManager entry point method called by RecyclerView returns.</p> 8442 * 8443 * @param index Index of the child to detach 8444 */ 8445 public void detachViewAt(int index) { 8446 detachViewInternal(index, getChildAt(index)); 8447 } 8448 8449 private void detachViewInternal(int index, View view) { 8450 if (DISPATCH_TEMP_DETACH) { 8451 ViewCompat.dispatchStartTemporaryDetach(view); 8452 } 8453 mChildHelper.detachViewFromParent(index); 8454 } 8455 8456 /** 8457 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8458 * This method should not be used to reattach views that were previously 8459 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8460 * 8461 * @param child Child to reattach 8462 * @param index Intended child index for child 8463 * @param lp LayoutParams for child 8464 */ 8465 public void attachView(View child, int index, LayoutParams lp) { 8466 ViewHolder vh = getChildViewHolderInt(child); 8467 if (vh.isRemoved()) { 8468 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); 8469 } else { 8470 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); 8471 } 8472 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); 8473 if (DISPATCH_TEMP_DETACH) { 8474 ViewCompat.dispatchFinishTemporaryDetach(child); 8475 } 8476 } 8477 8478 /** 8479 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8480 * This method should not be used to reattach views that were previously 8481 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8482 * 8483 * @param child Child to reattach 8484 * @param index Intended child index for child 8485 */ 8486 public void attachView(View child, int index) { 8487 attachView(child, index, (LayoutParams) child.getLayoutParams()); 8488 } 8489 8490 /** 8491 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8492 * This method should not be used to reattach views that were previously 8493 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8494 * 8495 * @param child Child to reattach 8496 */ 8497 public void attachView(View child) { 8498 attachView(child, -1); 8499 } 8500 8501 /** 8502 * Finish removing a view that was previously temporarily 8503 * {@link #detachView(android.view.View) detached}. 8504 * 8505 * @param child Detached child to remove 8506 */ 8507 public void removeDetachedView(View child) { 8508 mRecyclerView.removeDetachedView(child, false); 8509 } 8510 8511 /** 8512 * Moves a View from one position to another. 8513 * 8514 * @param fromIndex The View's initial index 8515 * @param toIndex The View's target index 8516 */ 8517 public void moveView(int fromIndex, int toIndex) { 8518 View view = getChildAt(fromIndex); 8519 if (view == null) { 8520 throw new IllegalArgumentException("Cannot move a child from non-existing index:" 8521 + fromIndex + mRecyclerView.toString()); 8522 } 8523 detachViewAt(fromIndex); 8524 attachView(view, toIndex); 8525 } 8526 8527 /** 8528 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 8529 * 8530 * <p>Scrapping a view allows it to be rebound and reused to show updated or 8531 * different data.</p> 8532 * 8533 * @param child Child to detach and scrap 8534 * @param recycler Recycler to deposit the new scrap view into 8535 */ 8536 public void detachAndScrapView(View child, Recycler recycler) { 8537 int index = mChildHelper.indexOfChild(child); 8538 scrapOrRecycleView(recycler, index, child); 8539 } 8540 8541 /** 8542 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 8543 * 8544 * <p>Scrapping a view allows it to be rebound and reused to show updated or 8545 * different data.</p> 8546 * 8547 * @param index Index of child to detach and scrap 8548 * @param recycler Recycler to deposit the new scrap view into 8549 */ 8550 public void detachAndScrapViewAt(int index, Recycler recycler) { 8551 final View child = getChildAt(index); 8552 scrapOrRecycleView(recycler, index, child); 8553 } 8554 8555 /** 8556 * Remove a child view and recycle it using the given Recycler. 8557 * 8558 * @param child Child to remove and recycle 8559 * @param recycler Recycler to use to recycle child 8560 */ 8561 public void removeAndRecycleView(View child, Recycler recycler) { 8562 removeView(child); 8563 recycler.recycleView(child); 8564 } 8565 8566 /** 8567 * Remove a child view and recycle it using the given Recycler. 8568 * 8569 * @param index Index of child to remove and recycle 8570 * @param recycler Recycler to use to recycle child 8571 */ 8572 public void removeAndRecycleViewAt(int index, Recycler recycler) { 8573 final View view = getChildAt(index); 8574 removeViewAt(index); 8575 recycler.recycleView(view); 8576 } 8577 8578 /** 8579 * Return the current number of child views attached to the parent RecyclerView. 8580 * This does not include child views that were temporarily detached and/or scrapped. 8581 * 8582 * @return Number of attached children 8583 */ 8584 public int getChildCount() { 8585 return mChildHelper != null ? mChildHelper.getChildCount() : 0; 8586 } 8587 8588 /** 8589 * Return the child view at the given index 8590 * @param index Index of child to return 8591 * @return Child view at index 8592 */ 8593 public View getChildAt(int index) { 8594 return mChildHelper != null ? mChildHelper.getChildAt(index) : null; 8595 } 8596 8597 /** 8598 * Return the width measurement spec mode that is currently relevant to the LayoutManager. 8599 * 8600 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 8601 * {@link #setAutoMeasureEnabled(boolean)}. 8602 * 8603 * <p>When RecyclerView is running a layout, this value is always set to 8604 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 8605 * 8606 * @return Width measure spec mode 8607 * 8608 * @see View.MeasureSpec#getMode(int) 8609 */ 8610 public int getWidthMode() { 8611 return mWidthMode; 8612 } 8613 8614 /** 8615 * Return the height measurement spec mode that is currently relevant to the LayoutManager. 8616 * 8617 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 8618 * {@link #setAutoMeasureEnabled(boolean)}. 8619 * 8620 * <p>When RecyclerView is running a layout, this value is always set to 8621 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 8622 * 8623 * @return Height measure spec mode 8624 * 8625 * @see View.MeasureSpec#getMode(int) 8626 */ 8627 public int getHeightMode() { 8628 return mHeightMode; 8629 } 8630 8631 /** 8632 * Returns the width that is currently relevant to the LayoutManager. 8633 * 8634 * <p>This value is usually equal to the laid out width of the {@link RecyclerView} but may 8635 * reflect the current {@link android.view.View.MeasureSpec} width if the 8636 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 8637 * measuring. The LayoutManager must always use this method to retrieve the width relevant 8638 * to it at any given time. 8639 * 8640 * @return Width in pixels 8641 */ 8642 public int getWidth() { 8643 return mWidth; 8644 } 8645 8646 /** 8647 * Returns the height that is currently relevant to the LayoutManager. 8648 * 8649 * <p>This value is usually equal to the laid out height of the {@link RecyclerView} but may 8650 * reflect the current {@link android.view.View.MeasureSpec} height if the 8651 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 8652 * measuring. The LayoutManager must always use this method to retrieve the height relevant 8653 * to it at any given time. 8654 * 8655 * @return Height in pixels 8656 */ 8657 public int getHeight() { 8658 return mHeight; 8659 } 8660 8661 /** 8662 * Return the left padding of the parent RecyclerView 8663 * 8664 * @return Padding in pixels 8665 */ 8666 public int getPaddingLeft() { 8667 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 8668 } 8669 8670 /** 8671 * Return the top padding of the parent RecyclerView 8672 * 8673 * @return Padding in pixels 8674 */ 8675 public int getPaddingTop() { 8676 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 8677 } 8678 8679 /** 8680 * Return the right padding of the parent RecyclerView 8681 * 8682 * @return Padding in pixels 8683 */ 8684 public int getPaddingRight() { 8685 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 8686 } 8687 8688 /** 8689 * Return the bottom padding of the parent RecyclerView 8690 * 8691 * @return Padding in pixels 8692 */ 8693 public int getPaddingBottom() { 8694 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 8695 } 8696 8697 /** 8698 * Return the start padding of the parent RecyclerView 8699 * 8700 * @return Padding in pixels 8701 */ 8702 public int getPaddingStart() { 8703 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 8704 } 8705 8706 /** 8707 * Return the end padding of the parent RecyclerView 8708 * 8709 * @return Padding in pixels 8710 */ 8711 public int getPaddingEnd() { 8712 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 8713 } 8714 8715 /** 8716 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 8717 * 8718 * @return True if the RecyclerView has focus, false otherwise. 8719 * @see View#isFocused() 8720 */ 8721 public boolean isFocused() { 8722 return mRecyclerView != null && mRecyclerView.isFocused(); 8723 } 8724 8725 /** 8726 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 8727 * 8728 * @return true if the RecyclerView has or contains focus 8729 * @see View#hasFocus() 8730 */ 8731 public boolean hasFocus() { 8732 return mRecyclerView != null && mRecyclerView.hasFocus(); 8733 } 8734 8735 /** 8736 * Returns the item View which has or contains focus. 8737 * 8738 * @return A direct child of RecyclerView which has focus or contains the focused child. 8739 */ 8740 public View getFocusedChild() { 8741 if (mRecyclerView == null) { 8742 return null; 8743 } 8744 final View focused = mRecyclerView.getFocusedChild(); 8745 if (focused == null || mChildHelper.isHidden(focused)) { 8746 return null; 8747 } 8748 return focused; 8749 } 8750 8751 /** 8752 * Returns the number of items in the adapter bound to the parent RecyclerView. 8753 * <p> 8754 * Note that this number is not necessarily equal to 8755 * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is 8756 * available, you should use {@link State#getItemCount() State#getItemCount()} instead. 8757 * For more details, check the documentation for 8758 * {@link State#getItemCount() State#getItemCount()}. 8759 * 8760 * @return The number of items in the bound adapter 8761 * @see State#getItemCount() 8762 */ 8763 public int getItemCount() { 8764 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 8765 return a != null ? a.getItemCount() : 0; 8766 } 8767 8768 /** 8769 * Offset all child views attached to the parent RecyclerView by dx pixels along 8770 * the horizontal axis. 8771 * 8772 * @param dx Pixels to offset by 8773 */ 8774 public void offsetChildrenHorizontal(int dx) { 8775 if (mRecyclerView != null) { 8776 mRecyclerView.offsetChildrenHorizontal(dx); 8777 } 8778 } 8779 8780 /** 8781 * Offset all child views attached to the parent RecyclerView by dy pixels along 8782 * the vertical axis. 8783 * 8784 * @param dy Pixels to offset by 8785 */ 8786 public void offsetChildrenVertical(int dy) { 8787 if (mRecyclerView != null) { 8788 mRecyclerView.offsetChildrenVertical(dy); 8789 } 8790 } 8791 8792 /** 8793 * Flags a view so that it will not be scrapped or recycled. 8794 * <p> 8795 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and 8796 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child 8797 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not 8798 * ignore the child. 8799 * <p> 8800 * Before this child can be recycled again, you have to call 8801 * {@link #stopIgnoringView(View)}. 8802 * <p> 8803 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 8804 * 8805 * @param view View to ignore. 8806 * @see #stopIgnoringView(View) 8807 */ 8808 public void ignoreView(View view) { 8809 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { 8810 // checking this because calling this method on a recycled or detached view may 8811 // cause loss of state. 8812 throw new IllegalArgumentException("View should be fully attached to be ignored" 8813 + mRecyclerView.exceptionLabel()); 8814 } 8815 final ViewHolder vh = getChildViewHolderInt(view); 8816 vh.addFlags(ViewHolder.FLAG_IGNORE); 8817 mRecyclerView.mViewInfoStore.removeViewHolder(vh); 8818 } 8819 8820 /** 8821 * View can be scrapped and recycled again. 8822 * <p> 8823 * Note that calling this method removes all information in the view holder. 8824 * <p> 8825 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 8826 * 8827 * @param view View to ignore. 8828 */ 8829 public void stopIgnoringView(View view) { 8830 final ViewHolder vh = getChildViewHolderInt(view); 8831 vh.stopIgnoring(); 8832 vh.resetInternal(); 8833 vh.addFlags(ViewHolder.FLAG_INVALID); 8834 } 8835 8836 /** 8837 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 8838 * into the given Recycler. The Recycler may prefer to reuse scrap views before 8839 * other views that were previously recycled. 8840 * 8841 * @param recycler Recycler to scrap views into 8842 */ 8843 public void detachAndScrapAttachedViews(Recycler recycler) { 8844 final int childCount = getChildCount(); 8845 for (int i = childCount - 1; i >= 0; i--) { 8846 final View v = getChildAt(i); 8847 scrapOrRecycleView(recycler, i, v); 8848 } 8849 } 8850 8851 private void scrapOrRecycleView(Recycler recycler, int index, View view) { 8852 final ViewHolder viewHolder = getChildViewHolderInt(view); 8853 if (viewHolder.shouldIgnore()) { 8854 if (DEBUG) { 8855 Log.d(TAG, "ignoring view " + viewHolder); 8856 } 8857 return; 8858 } 8859 if (viewHolder.isInvalid() && !viewHolder.isRemoved() 8860 && !mRecyclerView.mAdapter.hasStableIds()) { 8861 removeViewAt(index); 8862 recycler.recycleViewHolderInternal(viewHolder); 8863 } else { 8864 detachViewAt(index); 8865 recycler.scrapView(view); 8866 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); 8867 } 8868 } 8869 8870 /** 8871 * Recycles the scrapped views. 8872 * <p> 8873 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is 8874 * the expected behavior if scrapped views are used for animations. Otherwise, we need to 8875 * call remove and invalidate RecyclerView to ensure UI update. 8876 * 8877 * @param recycler Recycler 8878 */ 8879 void removeAndRecycleScrapInt(Recycler recycler) { 8880 final int scrapCount = recycler.getScrapCount(); 8881 // Loop backward, recycler might be changed by removeDetachedView() 8882 for (int i = scrapCount - 1; i >= 0; i--) { 8883 final View scrap = recycler.getScrapViewAt(i); 8884 final ViewHolder vh = getChildViewHolderInt(scrap); 8885 if (vh.shouldIgnore()) { 8886 continue; 8887 } 8888 // If the scrap view is animating, we need to cancel them first. If we cancel it 8889 // here, ItemAnimator callback may recycle it which will cause double recycling. 8890 // To avoid this, we mark it as not recycleable before calling the item animator. 8891 // Since removeDetachedView calls a user API, a common mistake (ending animations on 8892 // the view) may recycle it too, so we guard it before we call user APIs. 8893 vh.setIsRecyclable(false); 8894 if (vh.isTmpDetached()) { 8895 mRecyclerView.removeDetachedView(scrap, false); 8896 } 8897 if (mRecyclerView.mItemAnimator != null) { 8898 mRecyclerView.mItemAnimator.endAnimation(vh); 8899 } 8900 vh.setIsRecyclable(true); 8901 recycler.quickRecycleScrapView(scrap); 8902 } 8903 recycler.clearScrap(); 8904 if (scrapCount > 0) { 8905 mRecyclerView.invalidate(); 8906 } 8907 } 8908 8909 8910 /** 8911 * Measure a child view using standard measurement policy, taking the padding 8912 * of the parent RecyclerView and any added item decorations into account. 8913 * 8914 * <p>If the RecyclerView can be scrolled in either dimension the caller may 8915 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 8916 * 8917 * @param child Child view to measure 8918 * @param widthUsed Width in pixels currently consumed by other views, if relevant 8919 * @param heightUsed Height in pixels currently consumed by other views, if relevant 8920 */ 8921 public void measureChild(View child, int widthUsed, int heightUsed) { 8922 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 8923 8924 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 8925 widthUsed += insets.left + insets.right; 8926 heightUsed += insets.top + insets.bottom; 8927 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 8928 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 8929 canScrollHorizontally()); 8930 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 8931 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 8932 canScrollVertically()); 8933 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 8934 child.measure(widthSpec, heightSpec); 8935 } 8936 } 8937 8938 /** 8939 * RecyclerView internally does its own View measurement caching which should help with 8940 * WRAP_CONTENT. 8941 * <p> 8942 * Use this method if the View is already measured once in this layout pass. 8943 */ 8944 boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 8945 return !mMeasurementCacheEnabled 8946 || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) 8947 || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); 8948 } 8949 8950 // we may consider making this public 8951 /** 8952 * RecyclerView internally does its own View measurement caching which should help with 8953 * WRAP_CONTENT. 8954 * <p> 8955 * Use this method if the View is not yet measured and you need to decide whether to 8956 * measure this View or not. 8957 */ 8958 boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 8959 return child.isLayoutRequested() 8960 || !mMeasurementCacheEnabled 8961 || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) 8962 || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); 8963 } 8964 8965 /** 8966 * In addition to the View Framework's measurement cache, RecyclerView uses its own 8967 * additional measurement cache for its children to avoid re-measuring them when not 8968 * necessary. It is on by default but it can be turned off via 8969 * {@link #setMeasurementCacheEnabled(boolean)}. 8970 * 8971 * @return True if measurement cache is enabled, false otherwise. 8972 * 8973 * @see #setMeasurementCacheEnabled(boolean) 8974 */ 8975 public boolean isMeasurementCacheEnabled() { 8976 return mMeasurementCacheEnabled; 8977 } 8978 8979 /** 8980 * Sets whether RecyclerView should use its own measurement cache for the children. This is 8981 * a more aggressive cache than the framework uses. 8982 * 8983 * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. 8984 * 8985 * @see #isMeasurementCacheEnabled() 8986 */ 8987 public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { 8988 mMeasurementCacheEnabled = measurementCacheEnabled; 8989 } 8990 8991 private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { 8992 final int specMode = MeasureSpec.getMode(spec); 8993 final int specSize = MeasureSpec.getSize(spec); 8994 if (dimension > 0 && childSize != dimension) { 8995 return false; 8996 } 8997 switch (specMode) { 8998 case MeasureSpec.UNSPECIFIED: 8999 return true; 9000 case MeasureSpec.AT_MOST: 9001 return specSize >= childSize; 9002 case MeasureSpec.EXACTLY: 9003 return specSize == childSize; 9004 } 9005 return false; 9006 } 9007 9008 /** 9009 * Measure a child view using standard measurement policy, taking the padding 9010 * of the parent RecyclerView, any added item decorations and the child margins 9011 * into account. 9012 * 9013 * <p>If the RecyclerView can be scrolled in either dimension the caller may 9014 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 9015 * 9016 * @param child Child view to measure 9017 * @param widthUsed Width in pixels currently consumed by other views, if relevant 9018 * @param heightUsed Height in pixels currently consumed by other views, if relevant 9019 */ 9020 public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { 9021 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 9022 9023 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 9024 widthUsed += insets.left + insets.right; 9025 heightUsed += insets.top + insets.bottom; 9026 9027 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 9028 getPaddingLeft() + getPaddingRight() 9029 + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 9030 canScrollHorizontally()); 9031 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 9032 getPaddingTop() + getPaddingBottom() 9033 + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 9034 canScrollVertically()); 9035 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 9036 child.measure(widthSpec, heightSpec); 9037 } 9038 } 9039 9040 /** 9041 * Calculate a MeasureSpec value for measuring a child view in one dimension. 9042 * 9043 * @param parentSize Size of the parent view where the child will be placed 9044 * @param padding Total space currently consumed by other elements of the parent 9045 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 9046 * Generally obtained from the child view's LayoutParams 9047 * @param canScroll true if the parent RecyclerView can scroll in this dimension 9048 * 9049 * @return a MeasureSpec value for the child view 9050 * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} 9051 */ 9052 @Deprecated 9053 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 9054 boolean canScroll) { 9055 int size = Math.max(0, parentSize - padding); 9056 int resultSize = 0; 9057 int resultMode = 0; 9058 if (canScroll) { 9059 if (childDimension >= 0) { 9060 resultSize = childDimension; 9061 resultMode = MeasureSpec.EXACTLY; 9062 } else { 9063 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 9064 // instead using UNSPECIFIED. 9065 resultSize = 0; 9066 resultMode = MeasureSpec.UNSPECIFIED; 9067 } 9068 } else { 9069 if (childDimension >= 0) { 9070 resultSize = childDimension; 9071 resultMode = MeasureSpec.EXACTLY; 9072 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9073 resultSize = size; 9074 // TODO this should be my spec. 9075 resultMode = MeasureSpec.EXACTLY; 9076 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9077 resultSize = size; 9078 resultMode = MeasureSpec.AT_MOST; 9079 } 9080 } 9081 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 9082 } 9083 9084 /** 9085 * Calculate a MeasureSpec value for measuring a child view in one dimension. 9086 * 9087 * @param parentSize Size of the parent view where the child will be placed 9088 * @param parentMode The measurement spec mode of the parent 9089 * @param padding Total space currently consumed by other elements of parent 9090 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 9091 * Generally obtained from the child view's LayoutParams 9092 * @param canScroll true if the parent RecyclerView can scroll in this dimension 9093 * 9094 * @return a MeasureSpec value for the child view 9095 */ 9096 public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, 9097 int childDimension, boolean canScroll) { 9098 int size = Math.max(0, parentSize - padding); 9099 int resultSize = 0; 9100 int resultMode = 0; 9101 if (canScroll) { 9102 if (childDimension >= 0) { 9103 resultSize = childDimension; 9104 resultMode = MeasureSpec.EXACTLY; 9105 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9106 switch (parentMode) { 9107 case MeasureSpec.AT_MOST: 9108 case MeasureSpec.EXACTLY: 9109 resultSize = size; 9110 resultMode = parentMode; 9111 break; 9112 case MeasureSpec.UNSPECIFIED: 9113 resultSize = 0; 9114 resultMode = MeasureSpec.UNSPECIFIED; 9115 break; 9116 } 9117 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9118 resultSize = 0; 9119 resultMode = MeasureSpec.UNSPECIFIED; 9120 } 9121 } else { 9122 if (childDimension >= 0) { 9123 resultSize = childDimension; 9124 resultMode = MeasureSpec.EXACTLY; 9125 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9126 resultSize = size; 9127 resultMode = parentMode; 9128 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9129 resultSize = size; 9130 if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { 9131 resultMode = MeasureSpec.AT_MOST; 9132 } else { 9133 resultMode = MeasureSpec.UNSPECIFIED; 9134 } 9135 9136 } 9137 } 9138 //noinspection WrongConstant 9139 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 9140 } 9141 9142 /** 9143 * Returns the measured width of the given child, plus the additional size of 9144 * any insets applied by {@link ItemDecoration ItemDecorations}. 9145 * 9146 * @param child Child view to query 9147 * @return child's measured width plus <code>ItemDecoration</code> insets 9148 * 9149 * @see View#getMeasuredWidth() 9150 */ 9151 public int getDecoratedMeasuredWidth(View child) { 9152 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9153 return child.getMeasuredWidth() + insets.left + insets.right; 9154 } 9155 9156 /** 9157 * Returns the measured height of the given child, plus the additional size of 9158 * any insets applied by {@link ItemDecoration ItemDecorations}. 9159 * 9160 * @param child Child view to query 9161 * @return child's measured height plus <code>ItemDecoration</code> insets 9162 * 9163 * @see View#getMeasuredHeight() 9164 */ 9165 public int getDecoratedMeasuredHeight(View child) { 9166 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9167 return child.getMeasuredHeight() + insets.top + insets.bottom; 9168 } 9169 9170 /** 9171 * Lay out the given child view within the RecyclerView using coordinates that 9172 * include any current {@link ItemDecoration ItemDecorations}. 9173 * 9174 * <p>LayoutManagers should prefer working in sizes and coordinates that include 9175 * item decoration insets whenever possible. This allows the LayoutManager to effectively 9176 * ignore decoration insets within measurement and layout code. See the following 9177 * methods:</p> 9178 * <ul> 9179 * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li> 9180 * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li> 9181 * <li>{@link #measureChild(View, int, int)}</li> 9182 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 9183 * <li>{@link #getDecoratedLeft(View)}</li> 9184 * <li>{@link #getDecoratedTop(View)}</li> 9185 * <li>{@link #getDecoratedRight(View)}</li> 9186 * <li>{@link #getDecoratedBottom(View)}</li> 9187 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 9188 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 9189 * </ul> 9190 * 9191 * @param child Child to lay out 9192 * @param left Left edge, with item decoration insets included 9193 * @param top Top edge, with item decoration insets included 9194 * @param right Right edge, with item decoration insets included 9195 * @param bottom Bottom edge, with item decoration insets included 9196 * 9197 * @see View#layout(int, int, int, int) 9198 * @see #layoutDecoratedWithMargins(View, int, int, int, int) 9199 */ 9200 public void layoutDecorated(View child, int left, int top, int right, int bottom) { 9201 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9202 child.layout(left + insets.left, top + insets.top, right - insets.right, 9203 bottom - insets.bottom); 9204 } 9205 9206 /** 9207 * Lay out the given child view within the RecyclerView using coordinates that 9208 * include any current {@link ItemDecoration ItemDecorations} and margins. 9209 * 9210 * <p>LayoutManagers should prefer working in sizes and coordinates that include 9211 * item decoration insets whenever possible. This allows the LayoutManager to effectively 9212 * ignore decoration insets within measurement and layout code. See the following 9213 * methods:</p> 9214 * <ul> 9215 * <li>{@link #layoutDecorated(View, int, int, int, int)}</li> 9216 * <li>{@link #measureChild(View, int, int)}</li> 9217 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 9218 * <li>{@link #getDecoratedLeft(View)}</li> 9219 * <li>{@link #getDecoratedTop(View)}</li> 9220 * <li>{@link #getDecoratedRight(View)}</li> 9221 * <li>{@link #getDecoratedBottom(View)}</li> 9222 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 9223 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 9224 * </ul> 9225 * 9226 * @param child Child to lay out 9227 * @param left Left edge, with item decoration insets and left margin included 9228 * @param top Top edge, with item decoration insets and top margin included 9229 * @param right Right edge, with item decoration insets and right margin included 9230 * @param bottom Bottom edge, with item decoration insets and bottom margin included 9231 * 9232 * @see View#layout(int, int, int, int) 9233 * @see #layoutDecorated(View, int, int, int, int) 9234 */ 9235 public void layoutDecoratedWithMargins(View child, int left, int top, int right, 9236 int bottom) { 9237 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 9238 final Rect insets = lp.mDecorInsets; 9239 child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, 9240 right - insets.right - lp.rightMargin, 9241 bottom - insets.bottom - lp.bottomMargin); 9242 } 9243 9244 /** 9245 * Calculates the bounding box of the View while taking into account its matrix changes 9246 * (translation, scale etc) with respect to the RecyclerView. 9247 * <p> 9248 * If {@code includeDecorInsets} is {@code true}, they are applied first before applying 9249 * the View's matrix so that the decor offsets also go through the same transformation. 9250 * 9251 * @param child The ItemView whose bounding box should be calculated. 9252 * @param includeDecorInsets True if the decor insets should be included in the bounding box 9253 * @param out The rectangle into which the output will be written. 9254 */ 9255 public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) { 9256 if (includeDecorInsets) { 9257 Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9258 out.set(-insets.left, -insets.top, 9259 child.getWidth() + insets.right, child.getHeight() + insets.bottom); 9260 } else { 9261 out.set(0, 0, child.getWidth(), child.getHeight()); 9262 } 9263 9264 if (mRecyclerView != null) { 9265 final Matrix childMatrix = child.getMatrix(); 9266 if (childMatrix != null && !childMatrix.isIdentity()) { 9267 final RectF tempRectF = mRecyclerView.mTempRectF; 9268 tempRectF.set(out); 9269 childMatrix.mapRect(tempRectF); 9270 out.set( 9271 (int) Math.floor(tempRectF.left), 9272 (int) Math.floor(tempRectF.top), 9273 (int) Math.ceil(tempRectF.right), 9274 (int) Math.ceil(tempRectF.bottom) 9275 ); 9276 } 9277 } 9278 out.offset(child.getLeft(), child.getTop()); 9279 } 9280 9281 /** 9282 * Returns the bounds of the view including its decoration and margins. 9283 * 9284 * @param view The view element to check 9285 * @param outBounds A rect that will receive the bounds of the element including its 9286 * decoration and margins. 9287 */ 9288 public void getDecoratedBoundsWithMargins(View view, Rect outBounds) { 9289 RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); 9290 } 9291 9292 /** 9293 * Returns the left edge of the given child view within its parent, offset by any applied 9294 * {@link ItemDecoration ItemDecorations}. 9295 * 9296 * @param child Child to query 9297 * @return Child left edge with offsets applied 9298 * @see #getLeftDecorationWidth(View) 9299 */ 9300 public int getDecoratedLeft(View child) { 9301 return child.getLeft() - getLeftDecorationWidth(child); 9302 } 9303 9304 /** 9305 * Returns the top edge of the given child view within its parent, offset by any applied 9306 * {@link ItemDecoration ItemDecorations}. 9307 * 9308 * @param child Child to query 9309 * @return Child top edge with offsets applied 9310 * @see #getTopDecorationHeight(View) 9311 */ 9312 public int getDecoratedTop(View child) { 9313 return child.getTop() - getTopDecorationHeight(child); 9314 } 9315 9316 /** 9317 * Returns the right edge of the given child view within its parent, offset by any applied 9318 * {@link ItemDecoration ItemDecorations}. 9319 * 9320 * @param child Child to query 9321 * @return Child right edge with offsets applied 9322 * @see #getRightDecorationWidth(View) 9323 */ 9324 public int getDecoratedRight(View child) { 9325 return child.getRight() + getRightDecorationWidth(child); 9326 } 9327 9328 /** 9329 * Returns the bottom edge of the given child view within its parent, offset by any applied 9330 * {@link ItemDecoration ItemDecorations}. 9331 * 9332 * @param child Child to query 9333 * @return Child bottom edge with offsets applied 9334 * @see #getBottomDecorationHeight(View) 9335 */ 9336 public int getDecoratedBottom(View child) { 9337 return child.getBottom() + getBottomDecorationHeight(child); 9338 } 9339 9340 /** 9341 * Calculates the item decor insets applied to the given child and updates the provided 9342 * Rect instance with the inset values. 9343 * <ul> 9344 * <li>The Rect's left is set to the total width of left decorations.</li> 9345 * <li>The Rect's top is set to the total height of top decorations.</li> 9346 * <li>The Rect's right is set to the total width of right decorations.</li> 9347 * <li>The Rect's bottom is set to total height of bottom decorations.</li> 9348 * </ul> 9349 * <p> 9350 * Note that item decorations are automatically calculated when one of the LayoutManager's 9351 * measure child methods is called. If you need to measure the child with custom specs via 9352 * {@link View#measure(int, int)}, you can use this method to get decorations. 9353 * 9354 * @param child The child view whose decorations should be calculated 9355 * @param outRect The Rect to hold result values 9356 */ 9357 public void calculateItemDecorationsForChild(View child, Rect outRect) { 9358 if (mRecyclerView == null) { 9359 outRect.set(0, 0, 0, 0); 9360 return; 9361 } 9362 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 9363 outRect.set(insets); 9364 } 9365 9366 /** 9367 * Returns the total height of item decorations applied to child's top. 9368 * <p> 9369 * Note that this value is not updated until the View is measured or 9370 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9371 * 9372 * @param child Child to query 9373 * @return The total height of item decorations applied to the child's top. 9374 * @see #getDecoratedTop(View) 9375 * @see #calculateItemDecorationsForChild(View, Rect) 9376 */ 9377 public int getTopDecorationHeight(View child) { 9378 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; 9379 } 9380 9381 /** 9382 * Returns the total height of item decorations applied to child's bottom. 9383 * <p> 9384 * Note that this value is not updated until the View is measured or 9385 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9386 * 9387 * @param child Child to query 9388 * @return The total height of item decorations applied to the child's bottom. 9389 * @see #getDecoratedBottom(View) 9390 * @see #calculateItemDecorationsForChild(View, Rect) 9391 */ 9392 public int getBottomDecorationHeight(View child) { 9393 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; 9394 } 9395 9396 /** 9397 * Returns the total width of item decorations applied to child's left. 9398 * <p> 9399 * Note that this value is not updated until the View is measured or 9400 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9401 * 9402 * @param child Child to query 9403 * @return The total width of item decorations applied to the child's left. 9404 * @see #getDecoratedLeft(View) 9405 * @see #calculateItemDecorationsForChild(View, Rect) 9406 */ 9407 public int getLeftDecorationWidth(View child) { 9408 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; 9409 } 9410 9411 /** 9412 * Returns the total width of item decorations applied to child's right. 9413 * <p> 9414 * Note that this value is not updated until the View is measured or 9415 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9416 * 9417 * @param child Child to query 9418 * @return The total width of item decorations applied to the child's right. 9419 * @see #getDecoratedRight(View) 9420 * @see #calculateItemDecorationsForChild(View, Rect) 9421 */ 9422 public int getRightDecorationWidth(View child) { 9423 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; 9424 } 9425 9426 /** 9427 * Called when searching for a focusable view in the given direction has failed 9428 * for the current content of the RecyclerView. 9429 * 9430 * <p>This is the LayoutManager's opportunity to populate views in the given direction 9431 * to fulfill the request if it can. The LayoutManager should attach and return 9432 * the view to be focused, if a focusable view in the given direction is found. 9433 * Otherwise, if all the existing (or the newly populated views) are unfocusable, it returns 9434 * the next unfocusable view to become visible on the screen. This unfocusable view is 9435 * typically the first view that's either partially or fully out of RV's padded bounded 9436 * area in the given direction. The default implementation returns null.</p> 9437 * 9438 * @param focused The currently focused view 9439 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9440 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9441 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9442 * or 0 for not applicable 9443 * @param recycler The recycler to use for obtaining views for currently offscreen items 9444 * @param state Transient state of RecyclerView 9445 * @return The chosen view to be focused if a focusable view is found, otherwise an 9446 * unfocusable view to become visible onto the screen, else null. 9447 */ 9448 @Nullable 9449 public View onFocusSearchFailed(View focused, int direction, Recycler recycler, 9450 State state) { 9451 return null; 9452 } 9453 9454 /** 9455 * This method gives a LayoutManager an opportunity to intercept the initial focus search 9456 * before the default behavior of {@link FocusFinder} is used. If this method returns 9457 * null FocusFinder will attempt to find a focusable child view. If it fails 9458 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} 9459 * will be called to give the LayoutManager an opportunity to add new views for items 9460 * that did not have attached views representing them. The LayoutManager should not add 9461 * or remove views from this method. 9462 * 9463 * @param focused The currently focused view 9464 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9465 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9466 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9467 * @return A descendant view to focus or null to fall back to default behavior. 9468 * The default implementation returns null. 9469 */ 9470 public View onInterceptFocusSearch(View focused, int direction) { 9471 return null; 9472 } 9473 9474 /** 9475 * Returns the scroll amount that brings the given rect in child's coordinate system within 9476 * the padded area of RecyclerView. 9477 * @param parent The parent RecyclerView. 9478 * @param child The direct child making the request. 9479 * @param rect The rectangle in the child's coordinates the child 9480 * wishes to be on the screen. 9481 * @param immediate True to forbid animated or delayed scrolling, 9482 * false otherwise 9483 * @return The array containing the scroll amount in x and y directions that brings the 9484 * given rect into RV's padded area. 9485 */ 9486 private int[] getChildRectangleOnScreenScrollAmount(RecyclerView parent, View child, 9487 Rect rect, boolean immediate) { 9488 int[] out = new int[2]; 9489 final int parentLeft = getPaddingLeft(); 9490 final int parentTop = getPaddingTop(); 9491 final int parentRight = getWidth() - getPaddingRight(); 9492 final int parentBottom = getHeight() - getPaddingBottom(); 9493 final int childLeft = child.getLeft() + rect.left - child.getScrollX(); 9494 final int childTop = child.getTop() + rect.top - child.getScrollY(); 9495 final int childRight = childLeft + rect.width(); 9496 final int childBottom = childTop + rect.height(); 9497 9498 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 9499 final int offScreenTop = Math.min(0, childTop - parentTop); 9500 final int offScreenRight = Math.max(0, childRight - parentRight); 9501 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 9502 9503 // Favor the "start" layout direction over the end when bringing one side or the other 9504 // of a large rect into view. If we decide to bring in end because start is already 9505 // visible, limit the scroll such that start won't go out of bounds. 9506 final int dx; 9507 if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) { 9508 dx = offScreenRight != 0 ? offScreenRight 9509 : Math.max(offScreenLeft, childRight - parentRight); 9510 } else { 9511 dx = offScreenLeft != 0 ? offScreenLeft 9512 : Math.min(childLeft - parentLeft, offScreenRight); 9513 } 9514 9515 // Favor bringing the top into view over the bottom. If top is already visible and 9516 // we should scroll to make bottom visible, make sure top does not go out of bounds. 9517 final int dy = offScreenTop != 0 ? offScreenTop 9518 : Math.min(childTop - parentTop, offScreenBottom); 9519 out[0] = dx; 9520 out[1] = dy; 9521 return out; 9522 } 9523 /** 9524 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 9525 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 9526 * android.graphics.Rect, boolean)} for more details. 9527 * 9528 * <p>The base implementation will attempt to perform a standard programmatic scroll 9529 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 9530 * 9531 * @param child The direct child making the request. 9532 * @param rect The rectangle in the child's coordinates the child 9533 * wishes to be on the screen. 9534 * @param immediate True to forbid animated or delayed scrolling, 9535 * false otherwise 9536 * @return Whether the group scrolled to handle the operation 9537 */ 9538 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, 9539 boolean immediate) { 9540 return requestChildRectangleOnScreen(parent, child, rect, immediate, false); 9541 } 9542 9543 /** 9544 * Requests that the given child of the RecyclerView be positioned onto the screen. This 9545 * method can be called for both unfocusable and focusable child views. For unfocusable 9546 * child views, focusedChildVisible is typically true in which case, layout manager 9547 * makes the child view visible only if the currently focused child stays in-bounds of RV. 9548 * @param parent The parent RecyclerView. 9549 * @param child The direct child making the request. 9550 * @param rect The rectangle in the child's coordinates the child 9551 * wishes to be on the screen. 9552 * @param immediate True to forbid animated or delayed scrolling, 9553 * false otherwise 9554 * @param focusedChildVisible Whether the currently focused view must stay visible. 9555 * @return Whether the group scrolled to handle the operation 9556 */ 9557 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, 9558 boolean immediate, 9559 boolean focusedChildVisible) { 9560 int[] scrollAmount = getChildRectangleOnScreenScrollAmount(parent, child, rect, 9561 immediate); 9562 int dx = scrollAmount[0]; 9563 int dy = scrollAmount[1]; 9564 if (!focusedChildVisible || isFocusedChildVisibleAfterScrolling(parent, dx, dy)) { 9565 if (dx != 0 || dy != 0) { 9566 if (immediate) { 9567 parent.scrollBy(dx, dy); 9568 } else { 9569 parent.smoothScrollBy(dx, dy); 9570 } 9571 return true; 9572 } 9573 } 9574 return false; 9575 } 9576 9577 /** 9578 * Returns whether the given child view is partially or fully visible within the padded 9579 * bounded area of RecyclerView, depending on the input parameters. 9580 * A view is partially visible if it has non-zero overlap with RV's padded bounded area. 9581 * If acceptEndPointInclusion flag is set to true, it's also considered partially 9582 * visible if it's located outside RV's bounds and it's hitting either RV's start or end 9583 * bounds. 9584 * 9585 * @param child The child view to be examined. 9586 * @param completelyVisible If true, the method returns true if and only if the child is 9587 * completely visible. If false, the method returns true if and 9588 * only if the child is only partially visible (that is it will 9589 * return false if the child is either completely visible or out 9590 * of RV's bounds). 9591 * @param acceptEndPointInclusion If the view's endpoint intersection with RV's start of end 9592 * bounds is enough to consider it partially visible, 9593 * false otherwise. 9594 * @return True if the given child is partially or fully visible, false otherwise. 9595 */ 9596 public boolean isViewPartiallyVisible(@NonNull View child, boolean completelyVisible, 9597 boolean acceptEndPointInclusion) { 9598 int boundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS 9599 | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); 9600 boolean isViewFullyVisible = mHorizontalBoundCheck.isViewWithinBoundFlags(child, 9601 boundsFlag) 9602 && mVerticalBoundCheck.isViewWithinBoundFlags(child, boundsFlag); 9603 if (completelyVisible) { 9604 return isViewFullyVisible; 9605 } else { 9606 return !isViewFullyVisible; 9607 } 9608 } 9609 9610 /** 9611 * Returns whether the currently focused child stays within RV's bounds with the given 9612 * amount of scrolling. 9613 * @param parent The parent RecyclerView. 9614 * @param dx The scrolling in x-axis direction to be performed. 9615 * @param dy The scrolling in y-axis direction to be performed. 9616 * @return {@code false} if the focused child is not at least partially visible after 9617 * scrolling or no focused child exists, {@code true} otherwise. 9618 */ 9619 private boolean isFocusedChildVisibleAfterScrolling(RecyclerView parent, int dx, int dy) { 9620 final View focusedChild = parent.getFocusedChild(); 9621 if (focusedChild == null) { 9622 return false; 9623 } 9624 final int parentLeft = getPaddingLeft(); 9625 final int parentTop = getPaddingTop(); 9626 final int parentRight = getWidth() - getPaddingRight(); 9627 final int parentBottom = getHeight() - getPaddingBottom(); 9628 final Rect bounds = mRecyclerView.mTempRect; 9629 getDecoratedBoundsWithMargins(focusedChild, bounds); 9630 9631 if (bounds.left - dx >= parentRight || bounds.right - dx <= parentLeft 9632 || bounds.top - dy >= parentBottom || bounds.bottom - dy <= parentTop) { 9633 return false; 9634 } 9635 return true; 9636 } 9637 9638 /** 9639 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} 9640 */ 9641 @Deprecated 9642 public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { 9643 // eat the request if we are in the middle of a scroll or layout 9644 return isSmoothScrolling() || parent.isComputingLayout(); 9645 } 9646 9647 /** 9648 * Called when a descendant view of the RecyclerView requests focus. 9649 * 9650 * <p>A LayoutManager wishing to keep focused views aligned in a specific 9651 * portion of the view may implement that behavior in an override of this method.</p> 9652 * 9653 * <p>If the LayoutManager executes different behavior that should override the default 9654 * behavior of scrolling the focused child on screen instead of running alongside it, 9655 * this method should return true.</p> 9656 * 9657 * @param parent The RecyclerView hosting this LayoutManager 9658 * @param state Current state of RecyclerView 9659 * @param child Direct child of the RecyclerView containing the newly focused view 9660 * @param focused The newly focused view. This may be the same view as child or it may be 9661 * null 9662 * @return true if the default scroll behavior should be suppressed 9663 */ 9664 public boolean onRequestChildFocus(RecyclerView parent, State state, View child, 9665 View focused) { 9666 return onRequestChildFocus(parent, child, focused); 9667 } 9668 9669 /** 9670 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via 9671 * {@link RecyclerView#setAdapter(Adapter)} or 9672 * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this 9673 * opportunity to clear caches and configure state such that it can relayout appropriately 9674 * with the new data and potentially new view types. 9675 * 9676 * <p>The default implementation removes all currently attached views.</p> 9677 * 9678 * @param oldAdapter The previous adapter instance. Will be null if there was previously no 9679 * adapter. 9680 * @param newAdapter The new adapter instance. Might be null if 9681 * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}. 9682 */ 9683 public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { 9684 } 9685 9686 /** 9687 * Called to populate focusable views within the RecyclerView. 9688 * 9689 * <p>The LayoutManager implementation should return <code>true</code> if the default 9690 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 9691 * suppressed.</p> 9692 * 9693 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 9694 * to fall back to the default ViewGroup behavior.</p> 9695 * 9696 * @param recyclerView The RecyclerView hosting this LayoutManager 9697 * @param views List of output views. This method should add valid focusable views 9698 * to this list. 9699 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9700 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9701 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9702 * @param focusableMode The type of focusables to be added. 9703 * 9704 * @return true to suppress the default behavior, false to add default focusables after 9705 * this method returns. 9706 * 9707 * @see #FOCUSABLES_ALL 9708 * @see #FOCUSABLES_TOUCH_MODE 9709 */ 9710 public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, 9711 int direction, int focusableMode) { 9712 return false; 9713 } 9714 9715 /** 9716 * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or 9717 * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire 9718 * data set has changed. 9719 * 9720 * @param recyclerView 9721 */ 9722 public void onItemsChanged(RecyclerView recyclerView) { 9723 } 9724 9725 /** 9726 * Called when items have been added to the adapter. The LayoutManager may choose to 9727 * requestLayout if the inserted items would require refreshing the currently visible set 9728 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 9729 * 9730 * @param recyclerView 9731 * @param positionStart 9732 * @param itemCount 9733 */ 9734 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 9735 } 9736 9737 /** 9738 * Called when items have been removed from the adapter. 9739 * 9740 * @param recyclerView 9741 * @param positionStart 9742 * @param itemCount 9743 */ 9744 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { 9745 } 9746 9747 /** 9748 * Called when items have been changed in the adapter. 9749 * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)} 9750 * instead, then this callback will not be invoked. 9751 * 9752 * @param recyclerView 9753 * @param positionStart 9754 * @param itemCount 9755 */ 9756 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { 9757 } 9758 9759 /** 9760 * Called when items have been changed in the adapter and with optional payload. 9761 * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}. 9762 * 9763 * @param recyclerView 9764 * @param positionStart 9765 * @param itemCount 9766 * @param payload 9767 */ 9768 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, 9769 Object payload) { 9770 onItemsUpdated(recyclerView, positionStart, itemCount); 9771 } 9772 9773 /** 9774 * Called when an item is moved withing the adapter. 9775 * <p> 9776 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE 9777 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} 9778 * is called. 9779 * 9780 * @param recyclerView 9781 * @param from 9782 * @param to 9783 * @param itemCount 9784 */ 9785 public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { 9786 9787 } 9788 9789 9790 /** 9791 * <p>Override this method if you want to support scroll bars.</p> 9792 * 9793 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> 9794 * 9795 * <p>Default implementation returns 0.</p> 9796 * 9797 * @param state Current state of RecyclerView 9798 * @return The horizontal extent of the scrollbar's thumb 9799 * @see RecyclerView#computeHorizontalScrollExtent() 9800 */ 9801 public int computeHorizontalScrollExtent(State state) { 9802 return 0; 9803 } 9804 9805 /** 9806 * <p>Override this method if you want to support scroll bars.</p> 9807 * 9808 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> 9809 * 9810 * <p>Default implementation returns 0.</p> 9811 * 9812 * @param state Current State of RecyclerView where you can find total item count 9813 * @return The horizontal offset of the scrollbar's thumb 9814 * @see RecyclerView#computeHorizontalScrollOffset() 9815 */ 9816 public int computeHorizontalScrollOffset(State state) { 9817 return 0; 9818 } 9819 9820 /** 9821 * <p>Override this method if you want to support scroll bars.</p> 9822 * 9823 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> 9824 * 9825 * <p>Default implementation returns 0.</p> 9826 * 9827 * @param state Current State of RecyclerView where you can find total item count 9828 * @return The total horizontal range represented by the vertical scrollbar 9829 * @see RecyclerView#computeHorizontalScrollRange() 9830 */ 9831 public int computeHorizontalScrollRange(State state) { 9832 return 0; 9833 } 9834 9835 /** 9836 * <p>Override this method if you want to support scroll bars.</p> 9837 * 9838 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> 9839 * 9840 * <p>Default implementation returns 0.</p> 9841 * 9842 * @param state Current state of RecyclerView 9843 * @return The vertical extent of the scrollbar's thumb 9844 * @see RecyclerView#computeVerticalScrollExtent() 9845 */ 9846 public int computeVerticalScrollExtent(State state) { 9847 return 0; 9848 } 9849 9850 /** 9851 * <p>Override this method if you want to support scroll bars.</p> 9852 * 9853 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> 9854 * 9855 * <p>Default implementation returns 0.</p> 9856 * 9857 * @param state Current State of RecyclerView where you can find total item count 9858 * @return The vertical offset of the scrollbar's thumb 9859 * @see RecyclerView#computeVerticalScrollOffset() 9860 */ 9861 public int computeVerticalScrollOffset(State state) { 9862 return 0; 9863 } 9864 9865 /** 9866 * <p>Override this method if you want to support scroll bars.</p> 9867 * 9868 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> 9869 * 9870 * <p>Default implementation returns 0.</p> 9871 * 9872 * @param state Current State of RecyclerView where you can find total item count 9873 * @return The total vertical range represented by the vertical scrollbar 9874 * @see RecyclerView#computeVerticalScrollRange() 9875 */ 9876 public int computeVerticalScrollRange(State state) { 9877 return 0; 9878 } 9879 9880 /** 9881 * Measure the attached RecyclerView. Implementations must call 9882 * {@link #setMeasuredDimension(int, int)} before returning. 9883 * <p> 9884 * It is strongly advised to use the AutoMeasure mechanism by overriding 9885 * {@link #isAutoMeasureEnabled()} to return true as AutoMeasure handles all the standard 9886 * measure cases including when the RecyclerView's layout_width or layout_height have been 9887 * set to wrap_content. If {@link #isAutoMeasureEnabled()} is overridden to return true, 9888 * this method should not be overridden. 9889 * <p> 9890 * The default implementation will handle EXACTLY measurements and respect 9891 * the minimum width and height properties of the host RecyclerView if measured 9892 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView 9893 * will consume all available space. 9894 * 9895 * @param recycler Recycler 9896 * @param state Transient state of RecyclerView 9897 * @param widthSpec Width {@link android.view.View.MeasureSpec} 9898 * @param heightSpec Height {@link android.view.View.MeasureSpec} 9899 * 9900 * @see #isAutoMeasureEnabled() 9901 * @see #setMeasuredDimension(int, int) 9902 */ 9903 public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { 9904 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 9905 } 9906 9907 /** 9908 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the 9909 * host RecyclerView. 9910 * 9911 * @param widthSize Measured width 9912 * @param heightSize Measured height 9913 */ 9914 public void setMeasuredDimension(int widthSize, int heightSize) { 9915 mRecyclerView.setMeasuredDimension(widthSize, heightSize); 9916 } 9917 9918 /** 9919 * @return The host RecyclerView's {@link View#getMinimumWidth()} 9920 */ 9921 public int getMinimumWidth() { 9922 return ViewCompat.getMinimumWidth(mRecyclerView); 9923 } 9924 9925 /** 9926 * @return The host RecyclerView's {@link View#getMinimumHeight()} 9927 */ 9928 public int getMinimumHeight() { 9929 return ViewCompat.getMinimumHeight(mRecyclerView); 9930 } 9931 /** 9932 * <p>Called when the LayoutManager should save its state. This is a good time to save your 9933 * scroll position, configuration and anything else that may be required to restore the same 9934 * layout state if the LayoutManager is recreated.</p> 9935 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and 9936 * restore. This will let you share information between your LayoutManagers but it is also 9937 * your responsibility to make sure they use the same parcelable class.</p> 9938 * 9939 * @return Necessary information for LayoutManager to be able to restore its state 9940 */ 9941 public Parcelable onSaveInstanceState() { 9942 return null; 9943 } 9944 9945 9946 public void onRestoreInstanceState(Parcelable state) { 9947 9948 } 9949 9950 void stopSmoothScroller() { 9951 if (mSmoothScroller != null) { 9952 mSmoothScroller.stop(); 9953 } 9954 } 9955 9956 private void onSmoothScrollerStopped(SmoothScroller smoothScroller) { 9957 if (mSmoothScroller == smoothScroller) { 9958 mSmoothScroller = null; 9959 } 9960 } 9961 9962 /** 9963 * RecyclerView calls this method to notify LayoutManager that scroll state has changed. 9964 * 9965 * @param state The new scroll state for RecyclerView 9966 */ 9967 public void onScrollStateChanged(int state) { 9968 } 9969 9970 /** 9971 * Removes all views and recycles them using the given recycler. 9972 * <p> 9973 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. 9974 * <p> 9975 * If a View is marked as "ignored", it is not removed nor recycled. 9976 * 9977 * @param recycler Recycler to use to recycle children 9978 * @see #removeAndRecycleView(View, Recycler) 9979 * @see #removeAndRecycleViewAt(int, Recycler) 9980 * @see #ignoreView(View) 9981 */ 9982 public void removeAndRecycleAllViews(Recycler recycler) { 9983 for (int i = getChildCount() - 1; i >= 0; i--) { 9984 final View view = getChildAt(i); 9985 if (!getChildViewHolderInt(view).shouldIgnore()) { 9986 removeAndRecycleViewAt(i, recycler); 9987 } 9988 } 9989 } 9990 9991 // called by accessibility delegate 9992 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 9993 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info); 9994 } 9995 9996 /** 9997 * Called by the AccessibilityDelegate when the information about the current layout should 9998 * be populated. 9999 * <p> 10000 * Default implementation adds a {@link 10001 * androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. 10002 * <p> 10003 * You should override 10004 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 10005 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 10006 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and 10007 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for 10008 * more accurate accessibility information. 10009 * 10010 * @param recycler The Recycler that can be used to convert view positions into adapter 10011 * positions 10012 * @param state The current state of RecyclerView 10013 * @param info The info that should be filled by the LayoutManager 10014 * @see View#onInitializeAccessibilityNodeInfo( 10015 *android.view.accessibility.AccessibilityNodeInfo) 10016 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10017 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10018 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) 10019 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10020 */ 10021 public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state, 10022 AccessibilityNodeInfoCompat info) { 10023 if (mRecyclerView.canScrollVertically(-1) || mRecyclerView.canScrollHorizontally(-1)) { 10024 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 10025 info.setScrollable(true); 10026 } 10027 if (mRecyclerView.canScrollVertically(1) || mRecyclerView.canScrollHorizontally(1)) { 10028 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 10029 info.setScrollable(true); 10030 } 10031 final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo = 10032 AccessibilityNodeInfoCompat.CollectionInfoCompat 10033 .obtain(getRowCountForAccessibility(recycler, state), 10034 getColumnCountForAccessibility(recycler, state), 10035 isLayoutHierarchical(recycler, state), 10036 getSelectionModeForAccessibility(recycler, state)); 10037 info.setCollectionInfo(collectionInfo); 10038 } 10039 10040 // called by accessibility delegate 10041 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 10042 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); 10043 } 10044 10045 /** 10046 * Called by the accessibility delegate to initialize an accessibility event. 10047 * <p> 10048 * Default implementation adds item count and scroll information to the event. 10049 * 10050 * @param recycler The Recycler that can be used to convert view positions into adapter 10051 * positions 10052 * @param state The current state of RecyclerView 10053 * @param event The event instance to initialize 10054 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) 10055 */ 10056 public void onInitializeAccessibilityEvent(Recycler recycler, State state, 10057 AccessibilityEvent event) { 10058 if (mRecyclerView == null || event == null) { 10059 return; 10060 } 10061 event.setScrollable(mRecyclerView.canScrollVertically(1) 10062 || mRecyclerView.canScrollVertically(-1) 10063 || mRecyclerView.canScrollHorizontally(-1) 10064 || mRecyclerView.canScrollHorizontally(1)); 10065 10066 if (mRecyclerView.mAdapter != null) { 10067 event.setItemCount(mRecyclerView.mAdapter.getItemCount()); 10068 } 10069 } 10070 10071 // called by accessibility delegate 10072 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { 10073 final ViewHolder vh = getChildViewHolderInt(host); 10074 // avoid trying to create accessibility node info for removed children 10075 if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) { 10076 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, 10077 mRecyclerView.mState, host, info); 10078 } 10079 } 10080 10081 /** 10082 * Called by the AccessibilityDelegate when the accessibility information for a specific 10083 * item should be populated. 10084 * <p> 10085 * Default implementation adds basic positioning information about the item. 10086 * 10087 * @param recycler The Recycler that can be used to convert view positions into adapter 10088 * positions 10089 * @param state The current state of RecyclerView 10090 * @param host The child for which accessibility node info should be populated 10091 * @param info The info to fill out about the item 10092 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, 10093 * android.view.accessibility.AccessibilityNodeInfo) 10094 */ 10095 public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, 10096 View host, AccessibilityNodeInfoCompat info) { 10097 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; 10098 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; 10099 final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo = 10100 AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, 10101 columnIndexGuess, 1, false, false); 10102 info.setCollectionItemInfo(itemInfo); 10103 } 10104 10105 /** 10106 * A LayoutManager can call this method to force RecyclerView to run simple animations in 10107 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data 10108 * change). 10109 * <p> 10110 * Note that, calling this method will not guarantee that RecyclerView will run animations 10111 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will 10112 * not run any animations but will still clear this flag after the layout is complete. 10113 * 10114 */ 10115 public void requestSimpleAnimationsInNextLayout() { 10116 mRequestedSimpleAnimations = true; 10117 } 10118 10119 /** 10120 * Returns the selection mode for accessibility. Should be 10121 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, 10122 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or 10123 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. 10124 * <p> 10125 * Default implementation returns 10126 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 10127 * 10128 * @param recycler The Recycler that can be used to convert view positions into adapter 10129 * positions 10130 * @param state The current state of RecyclerView 10131 * @return Selection mode for accessibility. Default implementation returns 10132 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 10133 */ 10134 public int getSelectionModeForAccessibility(Recycler recycler, State state) { 10135 return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; 10136 } 10137 10138 /** 10139 * Returns the number of rows for accessibility. 10140 * <p> 10141 * Default implementation returns the number of items in the adapter if LayoutManager 10142 * supports vertical scrolling or 1 if LayoutManager does not support vertical 10143 * scrolling. 10144 * 10145 * @param recycler The Recycler that can be used to convert view positions into adapter 10146 * positions 10147 * @param state The current state of RecyclerView 10148 * @return The number of rows in LayoutManager for accessibility. 10149 */ 10150 public int getRowCountForAccessibility(Recycler recycler, State state) { 10151 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 10152 return 1; 10153 } 10154 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; 10155 } 10156 10157 /** 10158 * Returns the number of columns for accessibility. 10159 * <p> 10160 * Default implementation returns the number of items in the adapter if LayoutManager 10161 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal 10162 * scrolling. 10163 * 10164 * @param recycler The Recycler that can be used to convert view positions into adapter 10165 * positions 10166 * @param state The current state of RecyclerView 10167 * @return The number of rows in LayoutManager for accessibility. 10168 */ 10169 public int getColumnCountForAccessibility(Recycler recycler, State state) { 10170 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 10171 return 1; 10172 } 10173 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; 10174 } 10175 10176 /** 10177 * Returns whether layout is hierarchical or not to be used for accessibility. 10178 * <p> 10179 * Default implementation returns false. 10180 * 10181 * @param recycler The Recycler that can be used to convert view positions into adapter 10182 * positions 10183 * @param state The current state of RecyclerView 10184 * @return True if layout is hierarchical. 10185 */ 10186 public boolean isLayoutHierarchical(Recycler recycler, State state) { 10187 return false; 10188 } 10189 10190 // called by accessibility delegate 10191 boolean performAccessibilityAction(int action, Bundle args) { 10192 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, 10193 action, args); 10194 } 10195 10196 /** 10197 * Called by AccessibilityDelegate when an action is requested from the RecyclerView. 10198 * 10199 * @param recycler The Recycler that can be used to convert view positions into adapter 10200 * positions 10201 * @param state The current state of RecyclerView 10202 * @param action The action to perform 10203 * @param args Optional action arguments 10204 * @see View#performAccessibilityAction(int, android.os.Bundle) 10205 */ 10206 public boolean performAccessibilityAction(Recycler recycler, State state, int action, 10207 Bundle args) { 10208 if (mRecyclerView == null) { 10209 return false; 10210 } 10211 int vScroll = 0, hScroll = 0; 10212 switch (action) { 10213 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 10214 if (mRecyclerView.canScrollVertically(-1)) { 10215 vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); 10216 } 10217 if (mRecyclerView.canScrollHorizontally(-1)) { 10218 hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); 10219 } 10220 break; 10221 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 10222 if (mRecyclerView.canScrollVertically(1)) { 10223 vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); 10224 } 10225 if (mRecyclerView.canScrollHorizontally(1)) { 10226 hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); 10227 } 10228 break; 10229 } 10230 if (vScroll == 0 && hScroll == 0) { 10231 return false; 10232 } 10233 mRecyclerView.smoothScrollBy(hScroll, vScroll); 10234 return true; 10235 } 10236 10237 // called by accessibility delegate 10238 boolean performAccessibilityActionForItem(View view, int action, Bundle args) { 10239 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, 10240 view, action, args); 10241 } 10242 10243 /** 10244 * Called by AccessibilityDelegate when an accessibility action is requested on one of the 10245 * children of LayoutManager. 10246 * <p> 10247 * Default implementation does not do anything. 10248 * 10249 * @param recycler The Recycler that can be used to convert view positions into adapter 10250 * positions 10251 * @param state The current state of RecyclerView 10252 * @param view The child view on which the action is performed 10253 * @param action The action to perform 10254 * @param args Optional action arguments 10255 * @return true if action is handled 10256 * @see View#performAccessibilityAction(int, android.os.Bundle) 10257 */ 10258 public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view, 10259 int action, Bundle args) { 10260 return false; 10261 } 10262 10263 /** 10264 * Parse the xml attributes to get the most common properties used by layout managers. 10265 * 10266 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation 10267 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount 10268 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout 10269 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd 10270 * 10271 * @return an object containing the properties as specified in the attrs. 10272 */ 10273 public static Properties getProperties(Context context, AttributeSet attrs, 10274 int defStyleAttr, int defStyleRes) { 10275 Properties properties = new Properties(); 10276 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 10277 defStyleAttr, defStyleRes); 10278 properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation, 10279 DEFAULT_ORIENTATION); 10280 properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1); 10281 properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false); 10282 properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false); 10283 a.recycle(); 10284 return properties; 10285 } 10286 10287 void setExactMeasureSpecsFrom(RecyclerView recyclerView) { 10288 setMeasureSpecs( 10289 MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), 10290 MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) 10291 ); 10292 } 10293 10294 /** 10295 * Internal API to allow LayoutManagers to be measured twice. 10296 * <p> 10297 * This is not public because LayoutManagers should be able to handle their layouts in one 10298 * pass but it is very convenient to make existing LayoutManagers support wrapping content 10299 * when both orientations are undefined. 10300 * <p> 10301 * This API will be removed after default LayoutManagers properly implement wrap content in 10302 * non-scroll orientation. 10303 */ 10304 boolean shouldMeasureTwice() { 10305 return false; 10306 } 10307 10308 boolean hasFlexibleChildInBothOrientations() { 10309 final int childCount = getChildCount(); 10310 for (int i = 0; i < childCount; i++) { 10311 final View child = getChildAt(i); 10312 final ViewGroup.LayoutParams lp = child.getLayoutParams(); 10313 if (lp.width < 0 && lp.height < 0) { 10314 return true; 10315 } 10316 } 10317 return false; 10318 } 10319 10320 /** 10321 * Some general properties that a LayoutManager may want to use. 10322 */ 10323 public static class Properties { 10324 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation */ 10325 public int orientation; 10326 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount */ 10327 public int spanCount; 10328 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout */ 10329 public boolean reverseLayout; 10330 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd */ 10331 public boolean stackFromEnd; 10332 } 10333 } 10334 10335 /** 10336 * An ItemDecoration allows the application to add a special drawing and layout offset 10337 * to specific item views from the adapter's data set. This can be useful for drawing dividers 10338 * between items, highlights, visual grouping boundaries and more. 10339 * 10340 * <p>All ItemDecorations are drawn in the order they were added, before the item 10341 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} 10342 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, 10343 * RecyclerView.State)}.</p> 10344 */ 10345 public abstract static class ItemDecoration { 10346 /** 10347 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 10348 * Any content drawn by this method will be drawn before the item views are drawn, 10349 * and will thus appear underneath the views. 10350 * 10351 * @param c Canvas to draw into 10352 * @param parent RecyclerView this ItemDecoration is drawing into 10353 * @param state The current state of RecyclerView 10354 */ 10355 public void onDraw(Canvas c, RecyclerView parent, State state) { 10356 onDraw(c, parent); 10357 } 10358 10359 /** 10360 * @deprecated 10361 * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} 10362 */ 10363 @Deprecated 10364 public void onDraw(Canvas c, RecyclerView parent) { 10365 } 10366 10367 /** 10368 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 10369 * Any content drawn by this method will be drawn after the item views are drawn 10370 * and will thus appear over the views. 10371 * 10372 * @param c Canvas to draw into 10373 * @param parent RecyclerView this ItemDecoration is drawing into 10374 * @param state The current state of RecyclerView. 10375 */ 10376 public void onDrawOver(Canvas c, RecyclerView parent, State state) { 10377 onDrawOver(c, parent); 10378 } 10379 10380 /** 10381 * @deprecated 10382 * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} 10383 */ 10384 @Deprecated 10385 public void onDrawOver(Canvas c, RecyclerView parent) { 10386 } 10387 10388 10389 /** 10390 * @deprecated 10391 * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} 10392 */ 10393 @Deprecated 10394 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 10395 outRect.set(0, 0, 0, 0); 10396 } 10397 10398 /** 10399 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 10400 * the number of pixels that the item view should be inset by, similar to padding or margin. 10401 * The default implementation sets the bounds of outRect to 0 and returns. 10402 * 10403 * <p> 10404 * If this ItemDecoration does not affect the positioning of item views, it should set 10405 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 10406 * before returning. 10407 * 10408 * <p> 10409 * If you need to access Adapter for additional data, you can call 10410 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the 10411 * View. 10412 * 10413 * @param outRect Rect to receive the output. 10414 * @param view The child view to decorate 10415 * @param parent RecyclerView this ItemDecoration is decorating 10416 * @param state The current state of RecyclerView. 10417 */ 10418 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { 10419 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), 10420 parent); 10421 } 10422 } 10423 10424 /** 10425 * An OnItemTouchListener allows the application to intercept touch events in progress at the 10426 * view hierarchy level of the RecyclerView before those touch events are considered for 10427 * RecyclerView's own scrolling behavior. 10428 * 10429 * <p>This can be useful for applications that wish to implement various forms of gestural 10430 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 10431 * a touch interaction already in progress even if the RecyclerView is already handling that 10432 * gesture stream itself for the purposes of scrolling.</p> 10433 * 10434 * @see SimpleOnItemTouchListener 10435 */ 10436 public interface OnItemTouchListener { 10437 /** 10438 * Silently observe and/or take over touch events sent to the RecyclerView 10439 * before they are handled by either the RecyclerView itself or its child views. 10440 * 10441 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 10442 * in the order in which each listener was added, before any other touch processing 10443 * by the RecyclerView itself or child views occurs.</p> 10444 * 10445 * @param e MotionEvent describing the touch event. All coordinates are in 10446 * the RecyclerView's coordinate system. 10447 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 10448 * to continue with the current behavior and continue observing future events in 10449 * the gesture. 10450 */ 10451 boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 10452 10453 /** 10454 * Process a touch event as part of a gesture that was claimed by returning true from 10455 * a previous call to {@link #onInterceptTouchEvent}. 10456 * 10457 * @param e MotionEvent describing the touch event. All coordinates are in 10458 * the RecyclerView's coordinate system. 10459 */ 10460 void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 10461 10462 /** 10463 * Called when a child of RecyclerView does not want RecyclerView and its ancestors to 10464 * intercept touch events with 10465 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. 10466 * 10467 * @param disallowIntercept True if the child does not want the parent to 10468 * intercept touch events. 10469 * @see ViewParent#requestDisallowInterceptTouchEvent(boolean) 10470 */ 10471 void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); 10472 } 10473 10474 /** 10475 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies 10476 * and default return values. 10477 * <p> 10478 * You may prefer to extend this class if you don't need to override all methods. Another 10479 * benefit of using this class is future compatibility. As the interface may change, we'll 10480 * always provide a default implementation on this class so that your code won't break when 10481 * you update to a new version of the support library. 10482 */ 10483 public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener { 10484 @Override 10485 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 10486 return false; 10487 } 10488 10489 @Override 10490 public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 10491 } 10492 10493 @Override 10494 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 10495 } 10496 } 10497 10498 10499 /** 10500 * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event 10501 * has occurred on that RecyclerView. 10502 * <p> 10503 * @see RecyclerView#addOnScrollListener(OnScrollListener) 10504 * @see RecyclerView#clearOnChildAttachStateChangeListeners() 10505 * 10506 */ 10507 public abstract static class OnScrollListener { 10508 /** 10509 * Callback method to be invoked when RecyclerView's scroll state changes. 10510 * 10511 * @param recyclerView The RecyclerView whose scroll state has changed. 10512 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, 10513 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 10514 */ 10515 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){} 10516 10517 /** 10518 * Callback method to be invoked when the RecyclerView has been scrolled. This will be 10519 * called after the scroll has completed. 10520 * <p> 10521 * This callback will also be called if visible item range changes after a layout 10522 * calculation. In that case, dx and dy will be 0. 10523 * 10524 * @param recyclerView The RecyclerView which scrolled. 10525 * @param dx The amount of horizontal scroll. 10526 * @param dy The amount of vertical scroll. 10527 */ 10528 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){} 10529 } 10530 10531 /** 10532 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 10533 * a view is recycled. 10534 * 10535 * @see RecyclerView#setRecyclerListener(RecyclerListener) 10536 */ 10537 public interface RecyclerListener { 10538 10539 /** 10540 * This method is called whenever the view in the ViewHolder is recycled. 10541 * 10542 * RecyclerView calls this method right before clearing ViewHolder's internal data and 10543 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 10544 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 10545 * its adapter position. 10546 * 10547 * @param holder The ViewHolder containing the view that was recycled 10548 */ 10549 void onViewRecycled(@NonNull ViewHolder holder); 10550 } 10551 10552 /** 10553 * A Listener interface that can be attached to a RecylcerView to get notified 10554 * whenever a ViewHolder is attached to or detached from RecyclerView. 10555 */ 10556 public interface OnChildAttachStateChangeListener { 10557 10558 /** 10559 * Called when a view is attached to the RecyclerView. 10560 * 10561 * @param view The View which is attached to the RecyclerView 10562 */ 10563 void onChildViewAttachedToWindow(@NonNull View view); 10564 10565 /** 10566 * Called when a view is detached from RecyclerView. 10567 * 10568 * @param view The View which is being detached from the RecyclerView 10569 */ 10570 void onChildViewDetachedFromWindow(@NonNull View view); 10571 } 10572 10573 /** 10574 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 10575 * 10576 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 10577 * potentially expensive {@link View#findViewById(int)} results.</p> 10578 * 10579 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 10580 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 10581 * their own custom ViewHolder implementations to store data that makes binding view contents 10582 * easier. Implementations should assume that individual item views will hold strong references 10583 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 10584 * strong references to extra off-screen item views for caching purposes</p> 10585 */ 10586 public abstract static class ViewHolder { 10587 @NonNull 10588 public final View itemView; 10589 WeakReference<RecyclerView> mNestedRecyclerView; 10590 int mPosition = NO_POSITION; 10591 int mOldPosition = NO_POSITION; 10592 long mItemId = NO_ID; 10593 int mItemViewType = INVALID_TYPE; 10594 int mPreLayoutPosition = NO_POSITION; 10595 10596 // The item that this holder is shadowing during an item change event/animation 10597 ViewHolder mShadowedHolder = null; 10598 // The item that is shadowing this holder during an item change event/animation 10599 ViewHolder mShadowingHolder = null; 10600 10601 /** 10602 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 10603 * are all valid. 10604 */ 10605 static final int FLAG_BOUND = 1 << 0; 10606 10607 /** 10608 * The data this ViewHolder's view reflects is stale and needs to be rebound 10609 * by the adapter. mPosition and mItemId are consistent. 10610 */ 10611 static final int FLAG_UPDATE = 1 << 1; 10612 10613 /** 10614 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 10615 * are not to be trusted and may no longer match the item view type. 10616 * This ViewHolder must be fully rebound to different data. 10617 */ 10618 static final int FLAG_INVALID = 1 << 2; 10619 10620 /** 10621 * This ViewHolder points at data that represents an item previously removed from the 10622 * data set. Its view may still be used for things like outgoing animations. 10623 */ 10624 static final int FLAG_REMOVED = 1 << 3; 10625 10626 /** 10627 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 10628 * and is intended to keep views around during animations. 10629 */ 10630 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 10631 10632 /** 10633 * This ViewHolder is returned from scrap which means we are expecting an addView call 10634 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until 10635 * the end of the layout pass and then recycled by RecyclerView if it is not added back to 10636 * the RecyclerView. 10637 */ 10638 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; 10639 10640 /** 10641 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove 10642 * it unless LayoutManager is replaced. 10643 * It is still fully visible to the LayoutManager. 10644 */ 10645 static final int FLAG_IGNORE = 1 << 7; 10646 10647 /** 10648 * When the View is detached form the parent, we set this flag so that we can take correct 10649 * action when we need to remove it or add it back. 10650 */ 10651 static final int FLAG_TMP_DETACHED = 1 << 8; 10652 10653 /** 10654 * Set when we can no longer determine the adapter position of this ViewHolder until it is 10655 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is 10656 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon 10657 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is 10658 * re-calculated. 10659 */ 10660 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; 10661 10662 /** 10663 * Set when a addChangePayload(null) is called 10664 */ 10665 static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; 10666 10667 /** 10668 * Used by ItemAnimator when a ViewHolder's position changes 10669 */ 10670 static final int FLAG_MOVED = 1 << 11; 10671 10672 /** 10673 * Used by ItemAnimator when a ViewHolder appears in pre-layout 10674 */ 10675 static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; 10676 10677 static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; 10678 10679 /** 10680 * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from 10681 * hidden list (as if it was scrap) without being recycled in between. 10682 * 10683 * When a ViewHolder is hidden, there are 2 paths it can be re-used: 10684 * a) Animation ends, view is recycled and used from the recycle pool. 10685 * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. 10686 * 10687 * This flag is used to represent "case b" where the ViewHolder is reused without being 10688 * recycled (thus "bounced" from the hidden list). This state requires special handling 10689 * because the ViewHolder must be added to pre layout maps for animations as if it was 10690 * already there. 10691 */ 10692 static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; 10693 10694 /** 10695 * Flags that RecyclerView assigned {@link RecyclerViewAccessibilityDelegate 10696 * #getItemDelegate()} in onBindView when app does not provide a delegate. 10697 */ 10698 static final int FLAG_SET_A11Y_ITEM_DELEGATE = 1 << 14; 10699 10700 private int mFlags; 10701 10702 private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST; 10703 10704 List<Object> mPayloads = null; 10705 List<Object> mUnmodifiedPayloads = null; 10706 10707 private int mIsRecyclableCount = 0; 10708 10709 // If non-null, view is currently considered scrap and may be reused for other data by the 10710 // scrap container. 10711 private Recycler mScrapContainer = null; 10712 // Keeps whether this ViewHolder lives in Change scrap or Attached scrap 10713 private boolean mInChangeScrap = false; 10714 10715 // Saves isImportantForAccessibility value for the view item while it's in hidden state and 10716 // marked as unimportant for accessibility. 10717 private int mWasImportantForAccessibilityBeforeHidden = 10718 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 10719 // set if we defer the accessibility state change of the view holder 10720 @VisibleForTesting 10721 int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 10722 10723 /** 10724 * Is set when VH is bound from the adapter and cleaned right before it is sent to 10725 * {@link RecycledViewPool}. 10726 */ 10727 RecyclerView mOwnerRecyclerView; 10728 10729 public ViewHolder(@NonNull View itemView) { 10730 if (itemView == null) { 10731 throw new IllegalArgumentException("itemView may not be null"); 10732 } 10733 this.itemView = itemView; 10734 } 10735 10736 void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { 10737 addFlags(ViewHolder.FLAG_REMOVED); 10738 offsetPosition(offset, applyToPreLayout); 10739 mPosition = mNewPosition; 10740 } 10741 10742 void offsetPosition(int offset, boolean applyToPreLayout) { 10743 if (mOldPosition == NO_POSITION) { 10744 mOldPosition = mPosition; 10745 } 10746 if (mPreLayoutPosition == NO_POSITION) { 10747 mPreLayoutPosition = mPosition; 10748 } 10749 if (applyToPreLayout) { 10750 mPreLayoutPosition += offset; 10751 } 10752 mPosition += offset; 10753 if (itemView.getLayoutParams() != null) { 10754 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; 10755 } 10756 } 10757 10758 void clearOldPosition() { 10759 mOldPosition = NO_POSITION; 10760 mPreLayoutPosition = NO_POSITION; 10761 } 10762 10763 void saveOldPosition() { 10764 if (mOldPosition == NO_POSITION) { 10765 mOldPosition = mPosition; 10766 } 10767 } 10768 10769 boolean shouldIgnore() { 10770 return (mFlags & FLAG_IGNORE) != 0; 10771 } 10772 10773 /** 10774 * @deprecated This method is deprecated because its meaning is ambiguous due to the async 10775 * handling of adapter updates. Please use {@link #getLayoutPosition()} or 10776 * {@link #getAdapterPosition()} depending on your use case. 10777 * 10778 * @see #getLayoutPosition() 10779 * @see #getAdapterPosition() 10780 */ 10781 @Deprecated 10782 public final int getPosition() { 10783 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 10784 } 10785 10786 /** 10787 * Returns the position of the ViewHolder in terms of the latest layout pass. 10788 * <p> 10789 * This position is mostly used by RecyclerView components to be consistent while 10790 * RecyclerView lazily processes adapter updates. 10791 * <p> 10792 * For performance and animation reasons, RecyclerView batches all adapter updates until the 10793 * next layout pass. This may cause mismatches between the Adapter position of the item and 10794 * the position it had in the latest layout calculations. 10795 * <p> 10796 * LayoutManagers should always call this method while doing calculations based on item 10797 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, 10798 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position 10799 * of the item. 10800 * <p> 10801 * If LayoutManager needs to call an external method that requires the adapter position of 10802 * the item, it can use {@link #getAdapterPosition()} or 10803 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. 10804 * 10805 * @return Returns the adapter position of the ViewHolder in the latest layout pass. 10806 * @see #getAdapterPosition() 10807 */ 10808 public final int getLayoutPosition() { 10809 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 10810 } 10811 10812 /** 10813 * Returns the Adapter position of the item represented by this ViewHolder. 10814 * <p> 10815 * Note that this might be different than the {@link #getLayoutPosition()} if there are 10816 * pending adapter updates but a new layout pass has not happened yet. 10817 * <p> 10818 * RecyclerView does not handle any adapter updates until the next layout traversal. This 10819 * may create temporary inconsistencies between what user sees on the screen and what 10820 * adapter contents have. This inconsistency is not important since it will be less than 10821 * 16ms but it might be a problem if you want to use ViewHolder position to access the 10822 * adapter. Sometimes, you may need to get the exact adapter position to do 10823 * some actions in response to user events. In that case, you should use this method which 10824 * will calculate the Adapter position of the ViewHolder. 10825 * <p> 10826 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 10827 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 10828 * 10829 * @return The adapter position of the item if it still exists in the adapter. 10830 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 10831 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 10832 * layout pass or the ViewHolder has already been recycled. 10833 */ 10834 public final int getAdapterPosition() { 10835 if (mOwnerRecyclerView == null) { 10836 return NO_POSITION; 10837 } 10838 return mOwnerRecyclerView.getAdapterPositionFor(this); 10839 } 10840 10841 /** 10842 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders 10843 * to perform animations. 10844 * <p> 10845 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its 10846 * adapter index in the previous layout. 10847 * 10848 * @return The previous adapter index of the Item represented by this ViewHolder or 10849 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is 10850 * complete). 10851 */ 10852 public final int getOldPosition() { 10853 return mOldPosition; 10854 } 10855 10856 /** 10857 * Returns The itemId represented by this ViewHolder. 10858 * 10859 * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} 10860 * otherwise 10861 */ 10862 public final long getItemId() { 10863 return mItemId; 10864 } 10865 10866 /** 10867 * @return The view type of this ViewHolder. 10868 */ 10869 public final int getItemViewType() { 10870 return mItemViewType; 10871 } 10872 10873 boolean isScrap() { 10874 return mScrapContainer != null; 10875 } 10876 10877 void unScrap() { 10878 mScrapContainer.unscrapView(this); 10879 } 10880 10881 boolean wasReturnedFromScrap() { 10882 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; 10883 } 10884 10885 void clearReturnedFromScrapFlag() { 10886 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; 10887 } 10888 10889 void clearTmpDetachFlag() { 10890 mFlags = mFlags & ~FLAG_TMP_DETACHED; 10891 } 10892 10893 void stopIgnoring() { 10894 mFlags = mFlags & ~FLAG_IGNORE; 10895 } 10896 10897 void setScrapContainer(Recycler recycler, boolean isChangeScrap) { 10898 mScrapContainer = recycler; 10899 mInChangeScrap = isChangeScrap; 10900 } 10901 10902 boolean isInvalid() { 10903 return (mFlags & FLAG_INVALID) != 0; 10904 } 10905 10906 boolean needsUpdate() { 10907 return (mFlags & FLAG_UPDATE) != 0; 10908 } 10909 10910 boolean isBound() { 10911 return (mFlags & FLAG_BOUND) != 0; 10912 } 10913 10914 boolean isRemoved() { 10915 return (mFlags & FLAG_REMOVED) != 0; 10916 } 10917 10918 boolean hasAnyOfTheFlags(int flags) { 10919 return (mFlags & flags) != 0; 10920 } 10921 10922 boolean isTmpDetached() { 10923 return (mFlags & FLAG_TMP_DETACHED) != 0; 10924 } 10925 10926 boolean isAdapterPositionUnknown() { 10927 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); 10928 } 10929 10930 void setFlags(int flags, int mask) { 10931 mFlags = (mFlags & ~mask) | (flags & mask); 10932 } 10933 10934 void addFlags(int flags) { 10935 mFlags |= flags; 10936 } 10937 10938 void addChangePayload(Object payload) { 10939 if (payload == null) { 10940 addFlags(FLAG_ADAPTER_FULLUPDATE); 10941 } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 10942 createPayloadsIfNeeded(); 10943 mPayloads.add(payload); 10944 } 10945 } 10946 10947 private void createPayloadsIfNeeded() { 10948 if (mPayloads == null) { 10949 mPayloads = new ArrayList<Object>(); 10950 mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); 10951 } 10952 } 10953 10954 void clearPayload() { 10955 if (mPayloads != null) { 10956 mPayloads.clear(); 10957 } 10958 mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE; 10959 } 10960 10961 List<Object> getUnmodifiedPayloads() { 10962 if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 10963 if (mPayloads == null || mPayloads.size() == 0) { 10964 // Initial state, no update being called. 10965 return FULLUPDATE_PAYLOADS; 10966 } 10967 // there are none-null payloads 10968 return mUnmodifiedPayloads; 10969 } else { 10970 // a full update has been called. 10971 return FULLUPDATE_PAYLOADS; 10972 } 10973 } 10974 10975 void resetInternal() { 10976 mFlags = 0; 10977 mPosition = NO_POSITION; 10978 mOldPosition = NO_POSITION; 10979 mItemId = NO_ID; 10980 mPreLayoutPosition = NO_POSITION; 10981 mIsRecyclableCount = 0; 10982 mShadowedHolder = null; 10983 mShadowingHolder = null; 10984 clearPayload(); 10985 mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 10986 mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 10987 clearNestedRecyclerViewIfNotNested(this); 10988 } 10989 10990 /** 10991 * Called when the child view enters the hidden state 10992 */ 10993 private void onEnteredHiddenState(RecyclerView parent) { 10994 // While the view item is in hidden state, make it invisible for the accessibility. 10995 if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) { 10996 mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState; 10997 } else { 10998 mWasImportantForAccessibilityBeforeHidden = 10999 ViewCompat.getImportantForAccessibility(itemView); 11000 } 11001 parent.setChildImportantForAccessibilityInternal(this, 11002 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 11003 } 11004 11005 /** 11006 * Called when the child view leaves the hidden state 11007 */ 11008 private void onLeftHiddenState(RecyclerView parent) { 11009 parent.setChildImportantForAccessibilityInternal(this, 11010 mWasImportantForAccessibilityBeforeHidden); 11011 mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 11012 } 11013 11014 @Override 11015 public String toString() { 11016 final StringBuilder sb = new StringBuilder("ViewHolder{" 11017 + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId 11018 + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); 11019 if (isScrap()) { 11020 sb.append(" scrap ") 11021 .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); 11022 } 11023 if (isInvalid()) sb.append(" invalid"); 11024 if (!isBound()) sb.append(" unbound"); 11025 if (needsUpdate()) sb.append(" update"); 11026 if (isRemoved()) sb.append(" removed"); 11027 if (shouldIgnore()) sb.append(" ignored"); 11028 if (isTmpDetached()) sb.append(" tmpDetached"); 11029 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); 11030 if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); 11031 11032 if (itemView.getParent() == null) sb.append(" no parent"); 11033 sb.append("}"); 11034 return sb.toString(); 11035 } 11036 11037 /** 11038 * Informs the recycler whether this item can be recycled. Views which are not 11039 * recyclable will not be reused for other items until setIsRecyclable() is 11040 * later set to true. Calls to setIsRecyclable() should always be paired (one 11041 * call to setIsRecyclabe(false) should always be matched with a later call to 11042 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally 11043 * reference-counted. 11044 * 11045 * @param recyclable Whether this item is available to be recycled. Default value 11046 * is true. 11047 * 11048 * @see #isRecyclable() 11049 */ 11050 public final void setIsRecyclable(boolean recyclable) { 11051 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; 11052 if (mIsRecyclableCount < 0) { 11053 mIsRecyclableCount = 0; 11054 if (DEBUG) { 11055 throw new RuntimeException("isRecyclable decremented below 0: " 11056 + "unmatched pair of setIsRecyable() calls for " + this); 11057 } 11058 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " 11059 + "unmatched pair of setIsRecyable() calls for " + this); 11060 } else if (!recyclable && mIsRecyclableCount == 1) { 11061 mFlags |= FLAG_NOT_RECYCLABLE; 11062 } else if (recyclable && mIsRecyclableCount == 0) { 11063 mFlags &= ~FLAG_NOT_RECYCLABLE; 11064 } 11065 if (DEBUG) { 11066 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); 11067 } 11068 } 11069 11070 /** 11071 * @return true if this item is available to be recycled, false otherwise. 11072 * 11073 * @see #setIsRecyclable(boolean) 11074 */ 11075 public final boolean isRecyclable() { 11076 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 11077 && !ViewCompat.hasTransientState(itemView); 11078 } 11079 11080 /** 11081 * Returns whether we have animations referring to this view holder or not. 11082 * This is similar to isRecyclable flag but does not check transient state. 11083 */ 11084 private boolean shouldBeKeptAsChild() { 11085 return (mFlags & FLAG_NOT_RECYCLABLE) != 0; 11086 } 11087 11088 /** 11089 * @return True if ViewHolder is not referenced by RecyclerView animations but has 11090 * transient state which will prevent it from being recycled. 11091 */ 11092 private boolean doesTransientStatePreventRecycling() { 11093 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); 11094 } 11095 11096 boolean isUpdated() { 11097 return (mFlags & FLAG_UPDATE) != 0; 11098 } 11099 } 11100 11101 /** 11102 * This method is here so that we can control the important for a11y changes and test it. 11103 */ 11104 @VisibleForTesting 11105 boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, 11106 int importantForAccessibility) { 11107 if (isComputingLayout()) { 11108 viewHolder.mPendingAccessibilityState = importantForAccessibility; 11109 mPendingAccessibilityImportanceChange.add(viewHolder); 11110 return false; 11111 } 11112 ViewCompat.setImportantForAccessibility(viewHolder.itemView, importantForAccessibility); 11113 return true; 11114 } 11115 11116 void dispatchPendingImportantForAccessibilityChanges() { 11117 for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) { 11118 ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); 11119 if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { 11120 continue; 11121 } 11122 int state = viewHolder.mPendingAccessibilityState; 11123 if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { 11124 //noinspection WrongConstant 11125 ViewCompat.setImportantForAccessibility(viewHolder.itemView, state); 11126 viewHolder.mPendingAccessibilityState = 11127 ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; 11128 } 11129 } 11130 mPendingAccessibilityImportanceChange.clear(); 11131 } 11132 11133 int getAdapterPositionFor(ViewHolder viewHolder) { 11134 if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 11135 | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) 11136 || !viewHolder.isBound()) { 11137 return RecyclerView.NO_POSITION; 11138 } 11139 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); 11140 } 11141 11142 @VisibleForTesting 11143 void initFastScroller(StateListDrawable verticalThumbDrawable, 11144 Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, 11145 Drawable horizontalTrackDrawable) { 11146 if (verticalThumbDrawable == null || verticalTrackDrawable == null 11147 || horizontalThumbDrawable == null || horizontalTrackDrawable == null) { 11148 throw new IllegalArgumentException( 11149 "Trying to set fast scroller without both required drawables." + exceptionLabel()); 11150 } 11151 11152 Resources resources = getContext().getResources(); 11153 new FastScroller(this, verticalThumbDrawable, verticalTrackDrawable, 11154 horizontalThumbDrawable, horizontalTrackDrawable, 11155 resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness), 11156 resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range), 11157 resources.getDimensionPixelOffset(R.dimen.fastscroll_margin)); 11158 } 11159 11160 // NestedScrollingChild 11161 11162 @Override 11163 public void setNestedScrollingEnabled(boolean enabled) { 11164 getScrollingChildHelper().setNestedScrollingEnabled(enabled); 11165 } 11166 11167 @Override 11168 public boolean isNestedScrollingEnabled() { 11169 return getScrollingChildHelper().isNestedScrollingEnabled(); 11170 } 11171 11172 @Override 11173 public boolean startNestedScroll(int axes) { 11174 return getScrollingChildHelper().startNestedScroll(axes); 11175 } 11176 11177 @Override 11178 public boolean startNestedScroll(int axes, int type) { 11179 return getScrollingChildHelper().startNestedScroll(axes, type); 11180 } 11181 11182 @Override 11183 public void stopNestedScroll() { 11184 getScrollingChildHelper().stopNestedScroll(); 11185 } 11186 11187 @Override 11188 public void stopNestedScroll(int type) { 11189 getScrollingChildHelper().stopNestedScroll(type); 11190 } 11191 11192 @Override 11193 public boolean hasNestedScrollingParent() { 11194 return getScrollingChildHelper().hasNestedScrollingParent(); 11195 } 11196 11197 @Override 11198 public boolean hasNestedScrollingParent(int type) { 11199 return getScrollingChildHelper().hasNestedScrollingParent(type); 11200 } 11201 11202 @Override 11203 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 11204 int dyUnconsumed, int[] offsetInWindow) { 11205 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 11206 dxUnconsumed, dyUnconsumed, offsetInWindow); 11207 } 11208 11209 @Override 11210 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 11211 int dyUnconsumed, int[] offsetInWindow, int type) { 11212 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 11213 dxUnconsumed, dyUnconsumed, offsetInWindow, type); 11214 } 11215 11216 @Override 11217 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 11218 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 11219 } 11220 11221 @Override 11222 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, 11223 int type) { 11224 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, 11225 type); 11226 } 11227 11228 @Override 11229 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 11230 return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); 11231 } 11232 11233 @Override 11234 public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 11235 return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); 11236 } 11237 11238 /** 11239 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 11240 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 11241 * to create their own subclass of this <code>LayoutParams</code> class 11242 * to store any additional required per-child view metadata about the layout. 11243 */ 11244 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { 11245 ViewHolder mViewHolder; 11246 final Rect mDecorInsets = new Rect(); 11247 boolean mInsetsDirty = true; 11248 // Flag is set to true if the view is bound while it is detached from RV. 11249 // In this case, we need to manually call invalidate after view is added to guarantee that 11250 // invalidation is populated through the View hierarchy 11251 boolean mPendingInvalidate = false; 11252 11253 public LayoutParams(Context c, AttributeSet attrs) { 11254 super(c, attrs); 11255 } 11256 11257 public LayoutParams(int width, int height) { 11258 super(width, height); 11259 } 11260 11261 public LayoutParams(MarginLayoutParams source) { 11262 super(source); 11263 } 11264 11265 public LayoutParams(ViewGroup.LayoutParams source) { 11266 super(source); 11267 } 11268 11269 public LayoutParams(LayoutParams source) { 11270 super((ViewGroup.LayoutParams) source); 11271 } 11272 11273 /** 11274 * Returns true if the view this LayoutParams is attached to needs to have its content 11275 * updated from the corresponding adapter. 11276 * 11277 * @return true if the view should have its content updated 11278 */ 11279 public boolean viewNeedsUpdate() { 11280 return mViewHolder.needsUpdate(); 11281 } 11282 11283 /** 11284 * Returns true if the view this LayoutParams is attached to is now representing 11285 * potentially invalid data. A LayoutManager should scrap/recycle it. 11286 * 11287 * @return true if the view is invalid 11288 */ 11289 public boolean isViewInvalid() { 11290 return mViewHolder.isInvalid(); 11291 } 11292 11293 /** 11294 * Returns true if the adapter data item corresponding to the view this LayoutParams 11295 * is attached to has been removed from the data set. A LayoutManager may choose to 11296 * treat it differently in order to animate its outgoing or disappearing state. 11297 * 11298 * @return true if the item the view corresponds to was removed from the data set 11299 */ 11300 public boolean isItemRemoved() { 11301 return mViewHolder.isRemoved(); 11302 } 11303 11304 /** 11305 * Returns true if the adapter data item corresponding to the view this LayoutParams 11306 * is attached to has been changed in the data set. A LayoutManager may choose to 11307 * treat it differently in order to animate its changing state. 11308 * 11309 * @return true if the item the view corresponds to was changed in the data set 11310 */ 11311 public boolean isItemChanged() { 11312 return mViewHolder.isUpdated(); 11313 } 11314 11315 /** 11316 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} 11317 */ 11318 @Deprecated 11319 public int getViewPosition() { 11320 return mViewHolder.getPosition(); 11321 } 11322 11323 /** 11324 * Returns the adapter position that the view this LayoutParams is attached to corresponds 11325 * to as of latest layout calculation. 11326 * 11327 * @return the adapter position this view as of latest layout pass 11328 */ 11329 public int getViewLayoutPosition() { 11330 return mViewHolder.getLayoutPosition(); 11331 } 11332 11333 /** 11334 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 11335 * corresponds to. 11336 * 11337 * @return the up-to-date adapter position this view. It may return 11338 * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or 11339 * its up-to-date position cannot be calculated. 11340 */ 11341 public int getViewAdapterPosition() { 11342 return mViewHolder.getAdapterPosition(); 11343 } 11344 } 11345 11346 /** 11347 * Observer base class for watching changes to an {@link Adapter}. 11348 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 11349 */ 11350 public abstract static class AdapterDataObserver { 11351 public void onChanged() { 11352 // Do nothing 11353 } 11354 11355 public void onItemRangeChanged(int positionStart, int itemCount) { 11356 // do nothing 11357 } 11358 11359 public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { 11360 // fallback to onItemRangeChanged(positionStart, itemCount) if app 11361 // does not override this method. 11362 onItemRangeChanged(positionStart, itemCount); 11363 } 11364 11365 public void onItemRangeInserted(int positionStart, int itemCount) { 11366 // do nothing 11367 } 11368 11369 public void onItemRangeRemoved(int positionStart, int itemCount) { 11370 // do nothing 11371 } 11372 11373 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 11374 // do nothing 11375 } 11376 } 11377 11378 /** 11379 * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and 11380 * provides methods to trigger a programmatic scroll.</p> 11381 * 11382 * @see LinearSmoothScroller 11383 */ 11384 public abstract static class SmoothScroller { 11385 11386 private int mTargetPosition = RecyclerView.NO_POSITION; 11387 11388 private RecyclerView mRecyclerView; 11389 11390 private LayoutManager mLayoutManager; 11391 11392 private boolean mPendingInitialRun; 11393 11394 private boolean mRunning; 11395 11396 private View mTargetView; 11397 11398 private final Action mRecyclingAction; 11399 11400 public SmoothScroller() { 11401 mRecyclingAction = new Action(0, 0); 11402 } 11403 11404 /** 11405 * Starts a smooth scroll for the given target position. 11406 * <p>In each animation step, {@link RecyclerView} will check 11407 * for the target view and call either 11408 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 11409 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until 11410 * SmoothScroller is stopped.</p> 11411 * 11412 * <p>Note that if RecyclerView finds the target view, it will automatically stop the 11413 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will 11414 * stop calling SmoothScroller in each animation step.</p> 11415 */ 11416 void start(RecyclerView recyclerView, LayoutManager layoutManager) { 11417 mRecyclerView = recyclerView; 11418 mLayoutManager = layoutManager; 11419 if (mTargetPosition == RecyclerView.NO_POSITION) { 11420 throw new IllegalArgumentException("Invalid target position"); 11421 } 11422 mRecyclerView.mState.mTargetPosition = mTargetPosition; 11423 mRunning = true; 11424 mPendingInitialRun = true; 11425 mTargetView = findViewByPosition(getTargetPosition()); 11426 onStart(); 11427 mRecyclerView.mViewFlinger.postOnAnimation(); 11428 } 11429 11430 public void setTargetPosition(int targetPosition) { 11431 mTargetPosition = targetPosition; 11432 } 11433 11434 /** 11435 * Compute the scroll vector for a given target position. 11436 * <p> 11437 * This method can return null if the layout manager cannot calculate a scroll vector 11438 * for the given position (e.g. it has no current scroll position). 11439 * 11440 * @param targetPosition the position to which the scroller is scrolling 11441 * 11442 * @return the scroll vector for a given target position 11443 */ 11444 @Nullable 11445 public PointF computeScrollVectorForPosition(int targetPosition) { 11446 LayoutManager layoutManager = getLayoutManager(); 11447 if (layoutManager instanceof ScrollVectorProvider) { 11448 return ((ScrollVectorProvider) layoutManager) 11449 .computeScrollVectorForPosition(targetPosition); 11450 } 11451 Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager" 11452 + " does not implement " + ScrollVectorProvider.class.getCanonicalName()); 11453 return null; 11454 } 11455 11456 /** 11457 * @return The LayoutManager to which this SmoothScroller is attached. Will return 11458 * <code>null</code> after the SmoothScroller is stopped. 11459 */ 11460 @Nullable 11461 public LayoutManager getLayoutManager() { 11462 return mLayoutManager; 11463 } 11464 11465 /** 11466 * Stops running the SmoothScroller in each animation callback. Note that this does not 11467 * cancel any existing {@link Action} updated by 11468 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 11469 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. 11470 */ 11471 protected final void stop() { 11472 if (!mRunning) { 11473 return; 11474 } 11475 onStop(); 11476 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; 11477 mTargetView = null; 11478 mTargetPosition = RecyclerView.NO_POSITION; 11479 mPendingInitialRun = false; 11480 mRunning = false; 11481 // trigger a cleanup 11482 mLayoutManager.onSmoothScrollerStopped(this); 11483 // clear references to avoid any potential leak by a custom smooth scroller 11484 mLayoutManager = null; 11485 mRecyclerView = null; 11486 } 11487 11488 /** 11489 * Returns true if SmoothScroller has been started but has not received the first 11490 * animation 11491 * callback yet. 11492 * 11493 * @return True if this SmoothScroller is waiting to start 11494 */ 11495 public boolean isPendingInitialRun() { 11496 return mPendingInitialRun; 11497 } 11498 11499 11500 /** 11501 * @return True if SmoothScroller is currently active 11502 */ 11503 public boolean isRunning() { 11504 return mRunning; 11505 } 11506 11507 /** 11508 * Returns the adapter position of the target item 11509 * 11510 * @return Adapter position of the target item or 11511 * {@link RecyclerView#NO_POSITION} if no target view is set. 11512 */ 11513 public int getTargetPosition() { 11514 return mTargetPosition; 11515 } 11516 11517 private void onAnimation(int dx, int dy) { 11518 // TODO(b/72745539): If mRunning is false, we call stop, which is a no op if mRunning 11519 // is false. Also, if recyclerView is null, we call stop, and stop assumes recyclerView 11520 // is not null (as does the code following this block). This should be cleaned up. 11521 final RecyclerView recyclerView = mRecyclerView; 11522 if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) { 11523 stop(); 11524 } 11525 11526 // The following if block exists to have the LayoutManager scroll 1 pixel in the correct 11527 // direction in order to cause the LayoutManager to draw two pages worth of views so 11528 // that the target view may be found before scrolling any further. This is done to 11529 // prevent an initial scroll distance from scrolling past the view, which causes a 11530 // jittery looking animation. (This block also necessarily sets mPendingInitialRun to 11531 // false if it was true). 11532 if (mPendingInitialRun && mTargetView == null && mLayoutManager != null) { 11533 PointF pointF = computeScrollVectorForPosition(mTargetPosition); 11534 if (pointF != null && (pointF.x != 0 || pointF.y != 0)) { 11535 recyclerView.scrollStep( 11536 (int) Math.signum(pointF.x), 11537 (int) Math.signum(pointF.y), 11538 null); 11539 } 11540 } 11541 11542 mPendingInitialRun = false; 11543 11544 if (mTargetView != null) { 11545 // verify target position 11546 if (getChildPosition(mTargetView) == mTargetPosition) { 11547 onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction); 11548 mRecyclingAction.runIfNecessary(recyclerView); 11549 stop(); 11550 } else { 11551 Log.e(TAG, "Passed over target position while smooth scrolling."); 11552 mTargetView = null; 11553 } 11554 } 11555 if (mRunning) { 11556 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction); 11557 boolean hadJumpTarget = mRecyclingAction.hasJumpTarget(); 11558 mRecyclingAction.runIfNecessary(recyclerView); 11559 if (hadJumpTarget) { 11560 // It is not stopped so needs to be restarted 11561 if (mRunning) { 11562 mPendingInitialRun = true; 11563 recyclerView.mViewFlinger.postOnAnimation(); 11564 } else { 11565 // TODO(b/72745539): stop() is a no-op if mRunning is false, so this can be 11566 // removed. 11567 stop(); // done 11568 } 11569 } 11570 } 11571 } 11572 11573 /** 11574 * @see RecyclerView#getChildLayoutPosition(android.view.View) 11575 */ 11576 public int getChildPosition(View view) { 11577 return mRecyclerView.getChildLayoutPosition(view); 11578 } 11579 11580 /** 11581 * @see RecyclerView.LayoutManager#getChildCount() 11582 */ 11583 public int getChildCount() { 11584 return mRecyclerView.mLayout.getChildCount(); 11585 } 11586 11587 /** 11588 * @see RecyclerView.LayoutManager#findViewByPosition(int) 11589 */ 11590 public View findViewByPosition(int position) { 11591 return mRecyclerView.mLayout.findViewByPosition(position); 11592 } 11593 11594 /** 11595 * @see RecyclerView#scrollToPosition(int) 11596 * @deprecated Use {@link Action#jumpTo(int)}. 11597 */ 11598 @Deprecated 11599 public void instantScrollToPosition(int position) { 11600 mRecyclerView.scrollToPosition(position); 11601 } 11602 11603 protected void onChildAttachedToWindow(View child) { 11604 if (getChildPosition(child) == getTargetPosition()) { 11605 mTargetView = child; 11606 if (DEBUG) { 11607 Log.d(TAG, "smooth scroll target view has been attached"); 11608 } 11609 } 11610 } 11611 11612 /** 11613 * Normalizes the vector. 11614 * @param scrollVector The vector that points to the target scroll position 11615 */ 11616 protected void normalize(PointF scrollVector) { 11617 final float magnitude = (float) Math.sqrt(scrollVector.x * scrollVector.x 11618 + scrollVector.y * scrollVector.y); 11619 scrollVector.x /= magnitude; 11620 scrollVector.y /= magnitude; 11621 } 11622 11623 /** 11624 * Called when smooth scroll is started. This might be a good time to do setup. 11625 */ 11626 protected abstract void onStart(); 11627 11628 /** 11629 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. 11630 * @see #stop() 11631 */ 11632 protected abstract void onStop(); 11633 11634 /** 11635 * <p>RecyclerView will call this method each time it scrolls until it can find the target 11636 * position in the layout.</p> 11637 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the 11638 * provided {@link Action} to define the next scroll.</p> 11639 * 11640 * @param dx Last scroll amount horizontally 11641 * @param dy Last scroll amount vertically 11642 * @param state Transient state of RecyclerView 11643 * @param action If you want to trigger a new smooth scroll and cancel the previous one, 11644 * update this object. 11645 */ 11646 protected abstract void onSeekTargetStep(@Px int dx, @Px int dy, State state, 11647 Action action); 11648 11649 /** 11650 * Called when the target position is laid out. This is the last callback SmoothScroller 11651 * will receive and it should update the provided {@link Action} to define the scroll 11652 * details towards the target view. 11653 * @param targetView The view element which render the target position. 11654 * @param state Transient state of RecyclerView 11655 * @param action Action instance that you should update to define final scroll action 11656 * towards the targetView 11657 */ 11658 protected abstract void onTargetFound(View targetView, State state, Action action); 11659 11660 /** 11661 * Holds information about a smooth scroll request by a {@link SmoothScroller}. 11662 */ 11663 public static class Action { 11664 11665 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; 11666 11667 private int mDx; 11668 11669 private int mDy; 11670 11671 private int mDuration; 11672 11673 private int mJumpToPosition = NO_POSITION; 11674 11675 private Interpolator mInterpolator; 11676 11677 private boolean mChanged = false; 11678 11679 // we track this variable to inform custom implementer if they are updating the action 11680 // in every animation callback 11681 private int mConsecutiveUpdates = 0; 11682 11683 /** 11684 * @param dx Pixels to scroll horizontally 11685 * @param dy Pixels to scroll vertically 11686 */ 11687 public Action(@Px int dx, @Px int dy) { 11688 this(dx, dy, UNDEFINED_DURATION, null); 11689 } 11690 11691 /** 11692 * @param dx Pixels to scroll horizontally 11693 * @param dy Pixels to scroll vertically 11694 * @param duration Duration of the animation in milliseconds 11695 */ 11696 public Action(@Px int dx, @Px int dy, int duration) { 11697 this(dx, dy, duration, null); 11698 } 11699 11700 /** 11701 * @param dx Pixels to scroll horizontally 11702 * @param dy Pixels to scroll vertically 11703 * @param duration Duration of the animation in milliseconds 11704 * @param interpolator Interpolator to be used when calculating scroll position in each 11705 * animation step 11706 */ 11707 public Action(@Px int dx, @Px int dy, int duration, 11708 @Nullable Interpolator interpolator) { 11709 mDx = dx; 11710 mDy = dy; 11711 mDuration = duration; 11712 mInterpolator = interpolator; 11713 } 11714 11715 /** 11716 * Instead of specifying pixels to scroll, use the target position to jump using 11717 * {@link RecyclerView#scrollToPosition(int)}. 11718 * <p> 11719 * You may prefer using this method if scroll target is really far away and you prefer 11720 * to jump to a location and smooth scroll afterwards. 11721 * <p> 11722 * Note that calling this method takes priority over other update methods such as 11723 * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)}, 11724 * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call 11725 * {@link #jumpTo(int)}, the other changes will not be considered for this animation 11726 * frame. 11727 * 11728 * @param targetPosition The target item position to scroll to using instant scrolling. 11729 */ 11730 public void jumpTo(int targetPosition) { 11731 mJumpToPosition = targetPosition; 11732 } 11733 11734 boolean hasJumpTarget() { 11735 return mJumpToPosition >= 0; 11736 } 11737 11738 void runIfNecessary(RecyclerView recyclerView) { 11739 if (mJumpToPosition >= 0) { 11740 final int position = mJumpToPosition; 11741 mJumpToPosition = NO_POSITION; 11742 recyclerView.jumpToPositionForSmoothScroller(position); 11743 mChanged = false; 11744 return; 11745 } 11746 if (mChanged) { 11747 validate(); 11748 if (mInterpolator == null) { 11749 if (mDuration == UNDEFINED_DURATION) { 11750 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); 11751 } else { 11752 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); 11753 } 11754 } else { 11755 recyclerView.mViewFlinger.smoothScrollBy( 11756 mDx, mDy, mDuration, mInterpolator); 11757 } 11758 mConsecutiveUpdates++; 11759 if (mConsecutiveUpdates > 10) { 11760 // A new action is being set in every animation step. This looks like a bad 11761 // implementation. Inform developer. 11762 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" 11763 + " you are not changing it unless necessary"); 11764 } 11765 mChanged = false; 11766 } else { 11767 mConsecutiveUpdates = 0; 11768 } 11769 } 11770 11771 private void validate() { 11772 if (mInterpolator != null && mDuration < 1) { 11773 throw new IllegalStateException("If you provide an interpolator, you must" 11774 + " set a positive duration"); 11775 } else if (mDuration < 1) { 11776 throw new IllegalStateException("Scroll duration must be a positive number"); 11777 } 11778 } 11779 11780 @Px 11781 public int getDx() { 11782 return mDx; 11783 } 11784 11785 public void setDx(@Px int dx) { 11786 mChanged = true; 11787 mDx = dx; 11788 } 11789 11790 @Px 11791 public int getDy() { 11792 return mDy; 11793 } 11794 11795 public void setDy(@Px int dy) { 11796 mChanged = true; 11797 mDy = dy; 11798 } 11799 11800 public int getDuration() { 11801 return mDuration; 11802 } 11803 11804 public void setDuration(int duration) { 11805 mChanged = true; 11806 mDuration = duration; 11807 } 11808 11809 @Nullable 11810 public Interpolator getInterpolator() { 11811 return mInterpolator; 11812 } 11813 11814 /** 11815 * Sets the interpolator to calculate scroll steps 11816 * @param interpolator The interpolator to use. If you specify an interpolator, you must 11817 * also set the duration. 11818 * @see #setDuration(int) 11819 */ 11820 public void setInterpolator(@Nullable Interpolator interpolator) { 11821 mChanged = true; 11822 mInterpolator = interpolator; 11823 } 11824 11825 /** 11826 * Updates the action with given parameters. 11827 * @param dx Pixels to scroll horizontally 11828 * @param dy Pixels to scroll vertically 11829 * @param duration Duration of the animation in milliseconds 11830 * @param interpolator Interpolator to be used when calculating scroll position in each 11831 * animation step 11832 */ 11833 public void update(@Px int dx, @Px int dy, int duration, 11834 @Nullable Interpolator interpolator) { 11835 mDx = dx; 11836 mDy = dy; 11837 mDuration = duration; 11838 mInterpolator = interpolator; 11839 mChanged = true; 11840 } 11841 } 11842 11843 /** 11844 * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} 11845 * to provide a hint to a {@link SmoothScroller} about the location of the target position. 11846 */ 11847 public interface ScrollVectorProvider { 11848 /** 11849 * Should calculate the vector that points to the direction where the target position 11850 * can be found. 11851 * <p> 11852 * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards 11853 * the target position. 11854 * <p> 11855 * The magnitude of the vector is not important. It is always normalized before being 11856 * used by the {@link LinearSmoothScroller}. 11857 * <p> 11858 * LayoutManager should not check whether the position exists in the adapter or not. 11859 * 11860 * @param targetPosition the target position to which the returned vector should point 11861 * 11862 * @return the scroll vector for a given position. 11863 */ 11864 PointF computeScrollVectorForPosition(int targetPosition); 11865 } 11866 } 11867 11868 static class AdapterDataObservable extends Observable<AdapterDataObserver> { 11869 public boolean hasObservers() { 11870 return !mObservers.isEmpty(); 11871 } 11872 11873 public void notifyChanged() { 11874 // since onChanged() is implemented by the app, it could do anything, including 11875 // removing itself from {@link mObservers} - and that could cause problems if 11876 // an iterator is used on the ArrayList {@link mObservers}. 11877 // to avoid such problems, just march thru the list in the reverse order. 11878 for (int i = mObservers.size() - 1; i >= 0; i--) { 11879 mObservers.get(i).onChanged(); 11880 } 11881 } 11882 11883 public void notifyItemRangeChanged(int positionStart, int itemCount) { 11884 notifyItemRangeChanged(positionStart, itemCount, null); 11885 } 11886 11887 public void notifyItemRangeChanged(int positionStart, int itemCount, 11888 @Nullable Object payload) { 11889 // since onItemRangeChanged() is implemented by the app, it could do anything, including 11890 // removing itself from {@link mObservers} - and that could cause problems if 11891 // an iterator is used on the ArrayList {@link mObservers}. 11892 // to avoid such problems, just march thru the list in the reverse order. 11893 for (int i = mObservers.size() - 1; i >= 0; i--) { 11894 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); 11895 } 11896 } 11897 11898 public void notifyItemRangeInserted(int positionStart, int itemCount) { 11899 // since onItemRangeInserted() is implemented by the app, it could do anything, 11900 // including removing itself from {@link mObservers} - and that could cause problems if 11901 // an iterator is used on the ArrayList {@link mObservers}. 11902 // to avoid such problems, just march thru the list in the reverse order. 11903 for (int i = mObservers.size() - 1; i >= 0; i--) { 11904 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 11905 } 11906 } 11907 11908 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 11909 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 11910 // removing itself from {@link mObservers} - and that could cause problems if 11911 // an iterator is used on the ArrayList {@link mObservers}. 11912 // to avoid such problems, just march thru the list in the reverse order. 11913 for (int i = mObservers.size() - 1; i >= 0; i--) { 11914 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 11915 } 11916 } 11917 11918 public void notifyItemMoved(int fromPosition, int toPosition) { 11919 for (int i = mObservers.size() - 1; i >= 0; i--) { 11920 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); 11921 } 11922 } 11923 } 11924 11925 /** 11926 * This is public so that the CREATOR can be accessed on cold launch. 11927 * @hide 11928 */ 11929 @RestrictTo(LIBRARY_GROUP) 11930 public static class SavedState extends AbsSavedState { 11931 11932 Parcelable mLayoutState; 11933 11934 /** 11935 * called by CREATOR 11936 */ 11937 SavedState(Parcel in, ClassLoader loader) { 11938 super(in, loader); 11939 mLayoutState = in.readParcelable( 11940 loader != null ? loader : LayoutManager.class.getClassLoader()); 11941 } 11942 11943 /** 11944 * Called by onSaveInstanceState 11945 */ 11946 SavedState(Parcelable superState) { 11947 super(superState); 11948 } 11949 11950 @Override 11951 public void writeToParcel(Parcel dest, int flags) { 11952 super.writeToParcel(dest, flags); 11953 dest.writeParcelable(mLayoutState, 0); 11954 } 11955 11956 void copyFrom(SavedState other) { 11957 mLayoutState = other.mLayoutState; 11958 } 11959 11960 public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { 11961 @Override 11962 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 11963 return new SavedState(in, loader); 11964 } 11965 11966 @Override 11967 public SavedState createFromParcel(Parcel in) { 11968 return new SavedState(in, null); 11969 } 11970 11971 @Override 11972 public SavedState[] newArray(int size) { 11973 return new SavedState[size]; 11974 } 11975 }; 11976 } 11977 11978 /** 11979 * <p>Contains useful information about the current RecyclerView state like target scroll 11980 * position or view focus. State object can also keep arbitrary data, identified by resource 11981 * ids.</p> 11982 * <p>Often times, RecyclerView components will need to pass information between each other. 11983 * To provide a well defined data bus between components, RecyclerView passes the same State 11984 * object to component callbacks and these components can use it to exchange data.</p> 11985 * <p>If you implement custom components, you can use State's put/get/remove methods to pass 11986 * data between your components without needing to manage their lifecycles.</p> 11987 */ 11988 public static class State { 11989 static final int STEP_START = 1; 11990 static final int STEP_LAYOUT = 1 << 1; 11991 static final int STEP_ANIMATIONS = 1 << 2; 11992 11993 void assertLayoutStep(int accepted) { 11994 if ((accepted & mLayoutStep) == 0) { 11995 throw new IllegalStateException("Layout state should be one of " 11996 + Integer.toBinaryString(accepted) + " but it is " 11997 + Integer.toBinaryString(mLayoutStep)); 11998 } 11999 } 12000 12001 12002 /** Owned by SmoothScroller */ 12003 private int mTargetPosition = RecyclerView.NO_POSITION; 12004 12005 private SparseArray<Object> mData; 12006 12007 //////////////////////////////////////////////////////////////////////////////////////////// 12008 // Fields below are carried from one layout pass to the next 12009 //////////////////////////////////////////////////////////////////////////////////////////// 12010 12011 /** 12012 * Number of items adapter had in the previous layout. 12013 */ 12014 int mPreviousLayoutItemCount = 0; 12015 12016 /** 12017 * Number of items that were NOT laid out but has been deleted from the adapter after the 12018 * previous layout. 12019 */ 12020 int mDeletedInvisibleItemCountSincePreviousLayout = 0; 12021 12022 //////////////////////////////////////////////////////////////////////////////////////////// 12023 // Fields below must be updated or cleared before they are used (generally before a pass) 12024 //////////////////////////////////////////////////////////////////////////////////////////// 12025 12026 @IntDef(flag = true, value = { 12027 STEP_START, STEP_LAYOUT, STEP_ANIMATIONS 12028 }) 12029 @Retention(RetentionPolicy.SOURCE) 12030 @interface LayoutState {} 12031 12032 @LayoutState 12033 int mLayoutStep = STEP_START; 12034 12035 /** 12036 * Number of items adapter has. 12037 */ 12038 int mItemCount = 0; 12039 12040 boolean mStructureChanged = false; 12041 12042 /** 12043 * True if the associated {@link RecyclerView} is in the pre-layout step where it is having 12044 * its {@link LayoutManager} layout items where they will be at the beginning of a set of 12045 * predictive item animations. 12046 */ 12047 boolean mInPreLayout = false; 12048 12049 boolean mTrackOldChangeHolders = false; 12050 12051 boolean mIsMeasuring = false; 12052 12053 //////////////////////////////////////////////////////////////////////////////////////////// 12054 // Fields below are always reset outside of the pass (or passes) that use them 12055 //////////////////////////////////////////////////////////////////////////////////////////// 12056 12057 boolean mRunSimpleAnimations = false; 12058 12059 boolean mRunPredictiveAnimations = false; 12060 12061 /** 12062 * This data is saved before a layout calculation happens. After the layout is finished, 12063 * if the previously focused view has been replaced with another view for the same item, we 12064 * move the focus to the new item automatically. 12065 */ 12066 int mFocusedItemPosition; 12067 long mFocusedItemId; 12068 // when a sub child has focus, record its id and see if we can directly request focus on 12069 // that one instead 12070 int mFocusedSubChildId; 12071 12072 int mRemainingScrollHorizontal; 12073 int mRemainingScrollVertical; 12074 12075 //////////////////////////////////////////////////////////////////////////////////////////// 12076 12077 State reset() { 12078 mTargetPosition = RecyclerView.NO_POSITION; 12079 if (mData != null) { 12080 mData.clear(); 12081 } 12082 mItemCount = 0; 12083 mStructureChanged = false; 12084 mIsMeasuring = false; 12085 return this; 12086 } 12087 12088 /** 12089 * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially 12090 * prior to any layout passes. 12091 * 12092 * <p>Don't touch any state stored between layout passes, only reset per-layout state, so 12093 * that Recycler#getViewForPosition() can function safely.</p> 12094 */ 12095 void prepareForNestedPrefetch(Adapter adapter) { 12096 mLayoutStep = STEP_START; 12097 mItemCount = adapter.getItemCount(); 12098 mInPreLayout = false; 12099 mTrackOldChangeHolders = false; 12100 mIsMeasuring = false; 12101 } 12102 12103 /** 12104 * Returns true if the RecyclerView is currently measuring the layout. This value is 12105 * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView 12106 * has non-exact measurement specs. 12107 * <p> 12108 * Note that if the LayoutManager supports predictive animations and it is calculating the 12109 * pre-layout step, this value will be {@code false} even if the RecyclerView is in 12110 * {@code onMeasure} call. This is because pre-layout means the previous state of the 12111 * RecyclerView and measurements made for that state cannot change the RecyclerView's size. 12112 * LayoutManager is always guaranteed to receive another call to 12113 * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. 12114 * 12115 * @return True if the RecyclerView is currently calculating its bounds, false otherwise. 12116 */ 12117 public boolean isMeasuring() { 12118 return mIsMeasuring; 12119 } 12120 12121 /** 12122 * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its 12123 * {@link LayoutManager} layout items where they will be at the beginning of a set of 12124 * predictive item animations. 12125 */ 12126 public boolean isPreLayout() { 12127 return mInPreLayout; 12128 } 12129 12130 /** 12131 * Returns whether RecyclerView will run predictive animations in this layout pass 12132 * or not. 12133 * 12134 * @return true if RecyclerView is calculating predictive animations to be run at the end 12135 * of the layout pass. 12136 */ 12137 public boolean willRunPredictiveAnimations() { 12138 return mRunPredictiveAnimations; 12139 } 12140 12141 /** 12142 * Returns whether RecyclerView will run simple animations in this layout pass 12143 * or not. 12144 * 12145 * @return true if RecyclerView is calculating simple animations to be run at the end of 12146 * the layout pass. 12147 */ 12148 public boolean willRunSimpleAnimations() { 12149 return mRunSimpleAnimations; 12150 } 12151 12152 /** 12153 * Removes the mapping from the specified id, if there was any. 12154 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to 12155 * preserve cross functionality and avoid conflicts. 12156 */ 12157 public void remove(int resourceId) { 12158 if (mData == null) { 12159 return; 12160 } 12161 mData.remove(resourceId); 12162 } 12163 12164 /** 12165 * Gets the Object mapped from the specified id, or <code>null</code> 12166 * if no such data exists. 12167 * 12168 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* 12169 * to 12170 * preserve cross functionality and avoid conflicts. 12171 */ 12172 @SuppressWarnings("TypeParameterUnusedInFormals") 12173 public <T> T get(int resourceId) { 12174 if (mData == null) { 12175 return null; 12176 } 12177 return (T) mData.get(resourceId); 12178 } 12179 12180 /** 12181 * Adds a mapping from the specified id to the specified value, replacing the previous 12182 * mapping from the specified key if there was one. 12183 * 12184 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to 12185 * preserve cross functionality and avoid conflicts. 12186 * @param data The data you want to associate with the resourceId. 12187 */ 12188 public void put(int resourceId, Object data) { 12189 if (mData == null) { 12190 mData = new SparseArray<Object>(); 12191 } 12192 mData.put(resourceId, data); 12193 } 12194 12195 /** 12196 * If scroll is triggered to make a certain item visible, this value will return the 12197 * adapter index of that item. 12198 * @return Adapter index of the target item or 12199 * {@link RecyclerView#NO_POSITION} if there is no target 12200 * position. 12201 */ 12202 public int getTargetScrollPosition() { 12203 return mTargetPosition; 12204 } 12205 12206 /** 12207 * Returns if current scroll has a target position. 12208 * @return true if scroll is being triggered to make a certain position visible 12209 * @see #getTargetScrollPosition() 12210 */ 12211 public boolean hasTargetScrollPosition() { 12212 return mTargetPosition != RecyclerView.NO_POSITION; 12213 } 12214 12215 /** 12216 * @return true if the structure of the data set has changed since the last call to 12217 * onLayoutChildren, false otherwise 12218 */ 12219 public boolean didStructureChange() { 12220 return mStructureChanged; 12221 } 12222 12223 /** 12224 * Returns the total number of items that can be laid out. Note that this number is not 12225 * necessarily equal to the number of items in the adapter, so you should always use this 12226 * number for your position calculations and never access the adapter directly. 12227 * <p> 12228 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter 12229 * data changes on existing Views. These calculations are used to decide which animations 12230 * should be run. 12231 * <p> 12232 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to 12233 * present the correct state to LayoutManager in pre-layout pass. 12234 * <p> 12235 * For example, a newly added item is not included in pre-layout item count because 12236 * pre-layout reflects the contents of the adapter before the item is added. Behind the 12237 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that 12238 * LayoutManager does not know about the new item's existence in pre-layout. The item will 12239 * be available in second layout pass and will be included in the item count. Similar 12240 * adjustments are made for moved and removed items as well. 12241 * <p> 12242 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. 12243 * 12244 * @return The number of items currently available 12245 * @see LayoutManager#getItemCount() 12246 */ 12247 public int getItemCount() { 12248 return mInPreLayout 12249 ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) 12250 : mItemCount; 12251 } 12252 12253 /** 12254 * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/ 12255 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 12256 * other than {@link #SCROLL_STATE_SETTLING}. 12257 * 12258 * @return Remaining horizontal scroll distance 12259 */ 12260 public int getRemainingScrollHorizontal() { 12261 return mRemainingScrollHorizontal; 12262 } 12263 12264 /** 12265 * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/ 12266 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 12267 * other than {@link #SCROLL_STATE_SETTLING}. 12268 * 12269 * @return Remaining vertical scroll distance 12270 */ 12271 public int getRemainingScrollVertical() { 12272 return mRemainingScrollVertical; 12273 } 12274 12275 @Override 12276 public String toString() { 12277 return "State{" 12278 + "mTargetPosition=" + mTargetPosition 12279 + ", mData=" + mData 12280 + ", mItemCount=" + mItemCount 12281 + ", mIsMeasuring=" + mIsMeasuring 12282 + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount 12283 + ", mDeletedInvisibleItemCountSincePreviousLayout=" 12284 + mDeletedInvisibleItemCountSincePreviousLayout 12285 + ", mStructureChanged=" + mStructureChanged 12286 + ", mInPreLayout=" + mInPreLayout 12287 + ", mRunSimpleAnimations=" + mRunSimpleAnimations 12288 + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations 12289 + '}'; 12290 } 12291 } 12292 12293 /** 12294 * This class defines the behavior of fling if the developer wishes to handle it. 12295 * <p> 12296 * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. 12297 * 12298 * @see #setOnFlingListener(OnFlingListener) 12299 */ 12300 public abstract static class OnFlingListener { 12301 12302 /** 12303 * Override this to handle a fling given the velocities in both x and y directions. 12304 * Note that this method will only be called if the associated {@link LayoutManager} 12305 * supports scrolling and the fling is not handled by nested scrolls first. 12306 * 12307 * @param velocityX the fling velocity on the X axis 12308 * @param velocityY the fling velocity on the Y axis 12309 * 12310 * @return true if the fling was handled, false otherwise. 12311 */ 12312 public abstract boolean onFling(int velocityX, int velocityY); 12313 } 12314 12315 /** 12316 * Internal listener that manages items after animations finish. This is how items are 12317 * retained (not recycled) during animations, but allowed to be recycled afterwards. 12318 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() 12319 * method on the animator's listener when it is done animating any item. 12320 */ 12321 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { 12322 12323 ItemAnimatorRestoreListener() { 12324 } 12325 12326 @Override 12327 public void onAnimationFinished(ViewHolder item) { 12328 item.setIsRecyclable(true); 12329 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh 12330 item.mShadowedHolder = null; 12331 } 12332 // always null this because an OldViewHolder can never become NewViewHolder w/o being 12333 // recycled. 12334 item.mShadowingHolder = null; 12335 if (!item.shouldBeKeptAsChild()) { 12336 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { 12337 removeDetachedView(item.itemView, false); 12338 } 12339 } 12340 } 12341 } 12342 12343 /** 12344 * This class defines the animations that take place on items as changes are made 12345 * to the adapter. 12346 * 12347 * Subclasses of ItemAnimator can be used to implement custom animations for actions on 12348 * ViewHolder items. The RecyclerView will manage retaining these items while they 12349 * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} 12350 * when a ViewHolder's animation is finished. In other words, there must be a matching 12351 * {@link #dispatchAnimationFinished(ViewHolder)} call for each 12352 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, 12353 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12354 * animateChange()} 12355 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, 12356 * and 12357 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12358 * animateDisappearance()} call. 12359 * 12360 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p> 12361 * 12362 * @see #setItemAnimator(ItemAnimator) 12363 */ 12364 @SuppressWarnings("UnusedParameters") 12365 public abstract static class ItemAnimator { 12366 12367 /** 12368 * The Item represented by this ViewHolder is updated. 12369 * <p> 12370 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12371 */ 12372 public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; 12373 12374 /** 12375 * The Item represented by this ViewHolder is removed from the adapter. 12376 * <p> 12377 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12378 */ 12379 public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; 12380 12381 /** 12382 * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content 12383 * represented by this ViewHolder is invalid. 12384 * <p> 12385 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12386 */ 12387 public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; 12388 12389 /** 12390 * The position of the Item represented by this ViewHolder has been changed. This flag is 12391 * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to 12392 * any adapter change that may have a side effect on this item. (e.g. The item before this 12393 * one has been removed from the Adapter). 12394 * <p> 12395 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12396 */ 12397 public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; 12398 12399 /** 12400 * This ViewHolder was not laid out but has been added to the layout in pre-layout state 12401 * by the {@link LayoutManager}. This means that the item was already in the Adapter but 12402 * invisible and it may become visible in the post layout phase. LayoutManagers may prefer 12403 * to add new items in pre-layout to specify their virtual location when they are invisible 12404 * (e.g. to specify the item should <i>animate in</i> from below the visible area). 12405 * <p> 12406 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12407 */ 12408 public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 12409 ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; 12410 12411 /** 12412 * The set of flags that might be passed to 12413 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12414 */ 12415 @IntDef(flag = true, value = { 12416 FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, 12417 FLAG_APPEARED_IN_PRE_LAYOUT 12418 }) 12419 @Retention(RetentionPolicy.SOURCE) 12420 public @interface AdapterChanges {} 12421 private ItemAnimatorListener mListener = null; 12422 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = 12423 new ArrayList<ItemAnimatorFinishedListener>(); 12424 12425 private long mAddDuration = 120; 12426 private long mRemoveDuration = 120; 12427 private long mMoveDuration = 250; 12428 private long mChangeDuration = 250; 12429 12430 /** 12431 * Gets the current duration for which all move animations will run. 12432 * 12433 * @return The current move duration 12434 */ 12435 public long getMoveDuration() { 12436 return mMoveDuration; 12437 } 12438 12439 /** 12440 * Sets the duration for which all move animations will run. 12441 * 12442 * @param moveDuration The move duration 12443 */ 12444 public void setMoveDuration(long moveDuration) { 12445 mMoveDuration = moveDuration; 12446 } 12447 12448 /** 12449 * Gets the current duration for which all add animations will run. 12450 * 12451 * @return The current add duration 12452 */ 12453 public long getAddDuration() { 12454 return mAddDuration; 12455 } 12456 12457 /** 12458 * Sets the duration for which all add animations will run. 12459 * 12460 * @param addDuration The add duration 12461 */ 12462 public void setAddDuration(long addDuration) { 12463 mAddDuration = addDuration; 12464 } 12465 12466 /** 12467 * Gets the current duration for which all remove animations will run. 12468 * 12469 * @return The current remove duration 12470 */ 12471 public long getRemoveDuration() { 12472 return mRemoveDuration; 12473 } 12474 12475 /** 12476 * Sets the duration for which all remove animations will run. 12477 * 12478 * @param removeDuration The remove duration 12479 */ 12480 public void setRemoveDuration(long removeDuration) { 12481 mRemoveDuration = removeDuration; 12482 } 12483 12484 /** 12485 * Gets the current duration for which all change animations will run. 12486 * 12487 * @return The current change duration 12488 */ 12489 public long getChangeDuration() { 12490 return mChangeDuration; 12491 } 12492 12493 /** 12494 * Sets the duration for which all change animations will run. 12495 * 12496 * @param changeDuration The change duration 12497 */ 12498 public void setChangeDuration(long changeDuration) { 12499 mChangeDuration = changeDuration; 12500 } 12501 12502 /** 12503 * Internal only: 12504 * Sets the listener that must be called when the animator is finished 12505 * animating the item (or immediately if no animation happens). This is set 12506 * internally and is not intended to be set by external code. 12507 * 12508 * @param listener The listener that must be called. 12509 */ 12510 void setListener(ItemAnimatorListener listener) { 12511 mListener = listener; 12512 } 12513 12514 /** 12515 * Called by the RecyclerView before the layout begins. Item animator should record 12516 * necessary information about the View before it is potentially rebound, moved or removed. 12517 * <p> 12518 * The data returned from this method will be passed to the related <code>animate**</code> 12519 * methods. 12520 * <p> 12521 * Note that this method may be called after pre-layout phase if LayoutManager adds new 12522 * Views to the layout in pre-layout pass. 12523 * <p> 12524 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 12525 * the View and the adapter change flags. 12526 * 12527 * @param state The current State of RecyclerView which includes some useful data 12528 * about the layout that will be calculated. 12529 * @param viewHolder The ViewHolder whose information should be recorded. 12530 * @param changeFlags Additional information about what changes happened in the Adapter 12531 * about the Item represented by this ViewHolder. For instance, if 12532 * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. 12533 * @param payloads The payload list that was previously passed to 12534 * {@link Adapter#notifyItemChanged(int, Object)} or 12535 * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. 12536 * 12537 * @return An ItemHolderInfo instance that preserves necessary information about the 12538 * ViewHolder. This object will be passed back to related <code>animate**</code> methods 12539 * after layout is complete. 12540 * 12541 * @see #recordPostLayoutInformation(State, ViewHolder) 12542 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12543 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12544 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12545 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12546 */ 12547 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, 12548 @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, 12549 @NonNull List<Object> payloads) { 12550 return obtainHolderInfo().setFrom(viewHolder); 12551 } 12552 12553 /** 12554 * Called by the RecyclerView after the layout is complete. Item animator should record 12555 * necessary information about the View's final state. 12556 * <p> 12557 * The data returned from this method will be passed to the related <code>animate**</code> 12558 * methods. 12559 * <p> 12560 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 12561 * the View. 12562 * 12563 * @param state The current State of RecyclerView which includes some useful data about 12564 * the layout that will be calculated. 12565 * @param viewHolder The ViewHolder whose information should be recorded. 12566 * 12567 * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. 12568 * This object will be passed back to related <code>animate**</code> methods when 12569 * RecyclerView decides how items should be animated. 12570 * 12571 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12572 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12573 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12574 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12575 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12576 */ 12577 public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, 12578 @NonNull ViewHolder viewHolder) { 12579 return obtainHolderInfo().setFrom(viewHolder); 12580 } 12581 12582 /** 12583 * Called by the RecyclerView when a ViewHolder has disappeared from the layout. 12584 * <p> 12585 * This means that the View was a child of the LayoutManager when layout started but has 12586 * been removed by the LayoutManager. It might have been removed from the adapter or simply 12587 * become invisible due to other factors. You can distinguish these two cases by checking 12588 * the change flags that were passed to 12589 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12590 * <p> 12591 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 12592 * animation callback method which will be called by the RecyclerView depends on the 12593 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 12594 * LayoutManager's decision whether to layout the changed version of a disappearing 12595 * ViewHolder or not. RecyclerView will call 12596 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12597 * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator 12598 * returns {@code false} from 12599 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 12600 * LayoutManager lays out a new disappearing view that holds the updated information. 12601 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 12602 * <p> 12603 * If LayoutManager supports predictive animations, it might provide a target disappear 12604 * location for the View by laying it out in that location. When that happens, 12605 * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the 12606 * response of that call will be passed to this method as the <code>postLayoutInfo</code>. 12607 * <p> 12608 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 12609 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 12610 * decides not to animate the view). 12611 * 12612 * @param viewHolder The ViewHolder which should be animated 12613 * @param preLayoutInfo The information that was returned from 12614 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12615 * @param postLayoutInfo The information that was returned from 12616 * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be 12617 * null if the LayoutManager did not layout the item. 12618 * 12619 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12620 * false otherwise. 12621 */ 12622 public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, 12623 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); 12624 12625 /** 12626 * Called by the RecyclerView when a ViewHolder is added to the layout. 12627 * <p> 12628 * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started 12629 * but has been added by the LayoutManager. It might be newly added to the adapter or 12630 * simply become visible due to other factors. 12631 * <p> 12632 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 12633 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 12634 * decides not to animate the view). 12635 * 12636 * @param viewHolder The ViewHolder which should be animated 12637 * @param preLayoutInfo The information that was returned from 12638 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12639 * Might be null if Item was just added to the adapter or 12640 * LayoutManager does not support predictive animations or it could 12641 * not predict that this ViewHolder will become visible. 12642 * @param postLayoutInfo The information that was returned from {@link 12643 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12644 * 12645 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12646 * false otherwise. 12647 */ 12648 public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, 12649 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12650 12651 /** 12652 * Called by the RecyclerView when a ViewHolder is present in both before and after the 12653 * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call 12654 * for it or a {@link Adapter#notifyDataSetChanged()} call. 12655 * <p> 12656 * This ViewHolder still represents the same data that it was representing when the layout 12657 * started but its position / size may be changed by the LayoutManager. 12658 * <p> 12659 * If the Item's layout position didn't change, RecyclerView still calls this method because 12660 * it does not track this information (or does not necessarily know that an animation is 12661 * not required). Your ItemAnimator should handle this case and if there is nothing to 12662 * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return 12663 * <code>false</code>. 12664 * <p> 12665 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 12666 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 12667 * decides not to animate the view). 12668 * 12669 * @param viewHolder The ViewHolder which should be animated 12670 * @param preLayoutInfo The information that was returned from 12671 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12672 * @param postLayoutInfo The information that was returned from {@link 12673 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12674 * 12675 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12676 * false otherwise. 12677 */ 12678 public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, 12679 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12680 12681 /** 12682 * Called by the RecyclerView when an adapter item is present both before and after the 12683 * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call 12684 * for it. This method may also be called when 12685 * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that 12686 * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when 12687 * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called, 12688 * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be 12689 * called for the new ViewHolder and the old one will be recycled. 12690 * <p> 12691 * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is 12692 * a good possibility that item contents didn't really change but it is rebound from the 12693 * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the 12694 * screen didn't change and your animator should handle this case as well and avoid creating 12695 * unnecessary animations. 12696 * <p> 12697 * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the 12698 * previous presentation of the item as-is and supply a new ViewHolder for the updated 12699 * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. 12700 * This is useful if you don't know the contents of the Item and would like 12701 * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). 12702 * <p> 12703 * When you are writing a custom item animator for your layout, it might be more performant 12704 * and elegant to re-use the same ViewHolder and animate the content changes manually. 12705 * <p> 12706 * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. 12707 * If the Item's view type has changed or ItemAnimator returned <code>false</code> for 12708 * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the 12709 * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances 12710 * which represent the same Item. In that case, only the new ViewHolder is visible 12711 * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. 12712 * <p> 12713 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct 12714 * ViewHolder when their animation is complete 12715 * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to 12716 * animate the view). 12717 * <p> 12718 * If oldHolder and newHolder are the same instance, you should call 12719 * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>. 12720 * <p> 12721 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 12722 * animation callback method which will be called by the RecyclerView depends on the 12723 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 12724 * LayoutManager's decision whether to layout the changed version of a disappearing 12725 * ViewHolder or not. RecyclerView will call 12726 * {@code animateChange} instead of 12727 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12728 * animateDisappearance} if and only if the ItemAnimator returns {@code false} from 12729 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 12730 * LayoutManager lays out a new disappearing view that holds the updated information. 12731 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 12732 * 12733 * @param oldHolder The ViewHolder before the layout is started, might be the same 12734 * instance with newHolder. 12735 * @param newHolder The ViewHolder after the layout is finished, might be the same 12736 * instance with oldHolder. 12737 * @param preLayoutInfo The information that was returned from 12738 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12739 * @param postLayoutInfo The information that was returned from {@link 12740 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12741 * 12742 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12743 * false otherwise. 12744 */ 12745 public abstract boolean animateChange(@NonNull ViewHolder oldHolder, 12746 @NonNull ViewHolder newHolder, 12747 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12748 12749 @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { 12750 int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); 12751 if (viewHolder.isInvalid()) { 12752 return FLAG_INVALIDATED; 12753 } 12754 if ((flags & FLAG_INVALIDATED) == 0) { 12755 final int oldPos = viewHolder.getOldPosition(); 12756 final int pos = viewHolder.getAdapterPosition(); 12757 if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) { 12758 flags |= FLAG_MOVED; 12759 } 12760 } 12761 return flags; 12762 } 12763 12764 /** 12765 * Called when there are pending animations waiting to be started. This state 12766 * is governed by the return values from 12767 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12768 * animateAppearance()}, 12769 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12770 * animateChange()} 12771 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12772 * animatePersistence()}, and 12773 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12774 * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be 12775 * called later to start the associated animations. runPendingAnimations() will be scheduled 12776 * to be run on the next frame. 12777 */ 12778 public abstract void runPendingAnimations(); 12779 12780 /** 12781 * Method called when an animation on a view should be ended immediately. 12782 * This could happen when other events, like scrolling, occur, so that 12783 * animating views can be quickly put into their proper end locations. 12784 * Implementations should ensure that any animations running on the item 12785 * are canceled and affected properties are set to their end values. 12786 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 12787 * animation since the animations are effectively done when this method is called. 12788 * 12789 * @param item The item for which an animation should be stopped. 12790 */ 12791 public abstract void endAnimation(ViewHolder item); 12792 12793 /** 12794 * Method called when all item animations should be ended immediately. 12795 * This could happen when other events, like scrolling, occur, so that 12796 * animating views can be quickly put into their proper end locations. 12797 * Implementations should ensure that any animations running on any items 12798 * are canceled and affected properties are set to their end values. 12799 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 12800 * animation since the animations are effectively done when this method is called. 12801 */ 12802 public abstract void endAnimations(); 12803 12804 /** 12805 * Method which returns whether there are any item animations currently running. 12806 * This method can be used to determine whether to delay other actions until 12807 * animations end. 12808 * 12809 * @return true if there are any item animations currently running, false otherwise. 12810 */ 12811 public abstract boolean isRunning(); 12812 12813 /** 12814 * Method to be called by subclasses when an animation is finished. 12815 * <p> 12816 * For each call RecyclerView makes to 12817 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12818 * animateAppearance()}, 12819 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12820 * animatePersistence()}, or 12821 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12822 * animateDisappearance()}, there 12823 * should 12824 * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. 12825 * <p> 12826 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12827 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 12828 * and <code>newHolder</code> (if they are not the same instance). 12829 * 12830 * @param viewHolder The ViewHolder whose animation is finished. 12831 * @see #onAnimationFinished(ViewHolder) 12832 */ 12833 public final void dispatchAnimationFinished(ViewHolder viewHolder) { 12834 onAnimationFinished(viewHolder); 12835 if (mListener != null) { 12836 mListener.onAnimationFinished(viewHolder); 12837 } 12838 } 12839 12840 /** 12841 * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the 12842 * ItemAnimator. 12843 * 12844 * @param viewHolder The ViewHolder whose animation is finished. There might still be other 12845 * animations running on this ViewHolder. 12846 * @see #dispatchAnimationFinished(ViewHolder) 12847 */ 12848 public void onAnimationFinished(ViewHolder viewHolder) { 12849 } 12850 12851 /** 12852 * Method to be called by subclasses when an animation is started. 12853 * <p> 12854 * For each call RecyclerView makes to 12855 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12856 * animateAppearance()}, 12857 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12858 * animatePersistence()}, or 12859 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12860 * animateDisappearance()}, there should be a matching 12861 * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. 12862 * <p> 12863 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12864 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 12865 * and <code>newHolder</code> (if they are not the same instance). 12866 * <p> 12867 * If your ItemAnimator decides not to animate a ViewHolder, it should call 12868 * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling 12869 * {@link #dispatchAnimationStarted(ViewHolder)}. 12870 * 12871 * @param viewHolder The ViewHolder whose animation is starting. 12872 * @see #onAnimationStarted(ViewHolder) 12873 */ 12874 public final void dispatchAnimationStarted(ViewHolder viewHolder) { 12875 onAnimationStarted(viewHolder); 12876 } 12877 12878 /** 12879 * Called when a new animation is started on the given ViewHolder. 12880 * 12881 * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder 12882 * might already be animating and this might be another animation. 12883 * @see #dispatchAnimationStarted(ViewHolder) 12884 */ 12885 public void onAnimationStarted(ViewHolder viewHolder) { 12886 12887 } 12888 12889 /** 12890 * Like {@link #isRunning()}, this method returns whether there are any item 12891 * animations currently running. Additionally, the listener passed in will be called 12892 * when there are no item animations running, either immediately (before the method 12893 * returns) if no animations are currently running, or when the currently running 12894 * animations are {@link #dispatchAnimationsFinished() finished}. 12895 * 12896 * <p>Note that the listener is transient - it is either called immediately and not 12897 * stored at all, or stored only until it is called when running animations 12898 * are finished sometime later.</p> 12899 * 12900 * @param listener A listener to be called immediately if no animations are running 12901 * or later when currently-running animations have finished. A null listener is 12902 * equivalent to calling {@link #isRunning()}. 12903 * @return true if there are any item animations currently running, false otherwise. 12904 */ 12905 public final boolean isRunning(ItemAnimatorFinishedListener listener) { 12906 boolean running = isRunning(); 12907 if (listener != null) { 12908 if (!running) { 12909 listener.onAnimationsFinished(); 12910 } else { 12911 mFinishedListeners.add(listener); 12912 } 12913 } 12914 return running; 12915 } 12916 12917 /** 12918 * When an item is changed, ItemAnimator can decide whether it wants to re-use 12919 * the same ViewHolder for animations or RecyclerView should create a copy of the 12920 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 12921 * <p> 12922 * Note that this method will only be called if the {@link ViewHolder} still has the same 12923 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 12924 * both {@link ViewHolder}s in the 12925 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 12926 * <p> 12927 * If your application is using change payloads, you can override 12928 * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. 12929 * 12930 * @param viewHolder The ViewHolder which represents the changed item's old content. 12931 * 12932 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 12933 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 12934 * ItemAnimator to animate. Default implementation returns <code>true</code>. 12935 * 12936 * @see #canReuseUpdatedViewHolder(ViewHolder, List) 12937 */ 12938 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { 12939 return true; 12940 } 12941 12942 /** 12943 * When an item is changed, ItemAnimator can decide whether it wants to re-use 12944 * the same ViewHolder for animations or RecyclerView should create a copy of the 12945 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 12946 * <p> 12947 * Note that this method will only be called if the {@link ViewHolder} still has the same 12948 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 12949 * both {@link ViewHolder}s in the 12950 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 12951 * 12952 * @param viewHolder The ViewHolder which represents the changed item's old content. 12953 * @param payloads A non-null list of merged payloads that were sent with change 12954 * notifications. Can be empty if the adapter is invalidated via 12955 * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of 12956 * payloads will be passed into 12957 * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} 12958 * method <b>if</b> this method returns <code>true</code>. 12959 * 12960 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 12961 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 12962 * ItemAnimator to animate. Default implementation calls 12963 * {@link #canReuseUpdatedViewHolder(ViewHolder)}. 12964 * 12965 * @see #canReuseUpdatedViewHolder(ViewHolder) 12966 */ 12967 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, 12968 @NonNull List<Object> payloads) { 12969 return canReuseUpdatedViewHolder(viewHolder); 12970 } 12971 12972 /** 12973 * This method should be called by ItemAnimator implementations to notify 12974 * any listeners that all pending and active item animations are finished. 12975 */ 12976 public final void dispatchAnimationsFinished() { 12977 final int count = mFinishedListeners.size(); 12978 for (int i = 0; i < count; ++i) { 12979 mFinishedListeners.get(i).onAnimationsFinished(); 12980 } 12981 mFinishedListeners.clear(); 12982 } 12983 12984 /** 12985 * Returns a new {@link ItemHolderInfo} which will be used to store information about the 12986 * ViewHolder. This information will later be passed into <code>animate**</code> methods. 12987 * <p> 12988 * You can override this method if you want to extend {@link ItemHolderInfo} and provide 12989 * your own instances. 12990 * 12991 * @return A new {@link ItemHolderInfo}. 12992 */ 12993 public ItemHolderInfo obtainHolderInfo() { 12994 return new ItemHolderInfo(); 12995 } 12996 12997 /** 12998 * The interface to be implemented by listeners to animation events from this 12999 * ItemAnimator. This is used internally and is not intended for developers to 13000 * create directly. 13001 */ 13002 interface ItemAnimatorListener { 13003 void onAnimationFinished(ViewHolder item); 13004 } 13005 13006 /** 13007 * This interface is used to inform listeners when all pending or running animations 13008 * in an ItemAnimator are finished. This can be used, for example, to delay an action 13009 * in a data set until currently-running animations are complete. 13010 * 13011 * @see #isRunning(ItemAnimatorFinishedListener) 13012 */ 13013 public interface ItemAnimatorFinishedListener { 13014 /** 13015 * Notifies when all pending or running animations in an ItemAnimator are finished. 13016 */ 13017 void onAnimationsFinished(); 13018 } 13019 13020 /** 13021 * A simple data structure that holds information about an item's bounds. 13022 * This information is used in calculating item animations. Default implementation of 13023 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and 13024 * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data 13025 * structure. You can extend this class if you would like to keep more information about 13026 * the Views. 13027 * <p> 13028 * If you want to provide your own implementation but still use `super` methods to record 13029 * basic information, you can override {@link #obtainHolderInfo()} to provide your own 13030 * instances. 13031 */ 13032 public static class ItemHolderInfo { 13033 13034 /** 13035 * The left edge of the View (excluding decorations) 13036 */ 13037 public int left; 13038 13039 /** 13040 * The top edge of the View (excluding decorations) 13041 */ 13042 public int top; 13043 13044 /** 13045 * The right edge of the View (excluding decorations) 13046 */ 13047 public int right; 13048 13049 /** 13050 * The bottom edge of the View (excluding decorations) 13051 */ 13052 public int bottom; 13053 13054 /** 13055 * The change flags that were passed to 13056 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. 13057 */ 13058 @AdapterChanges 13059 public int changeFlags; 13060 13061 public ItemHolderInfo() { 13062 } 13063 13064 /** 13065 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 13066 * the given ViewHolder. Clears all {@link #changeFlags}. 13067 * 13068 * @param holder The ViewHolder whose bounds should be copied. 13069 * @return This {@link ItemHolderInfo} 13070 */ 13071 public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) { 13072 return setFrom(holder, 0); 13073 } 13074 13075 /** 13076 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 13077 * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. 13078 * 13079 * @param holder The ViewHolder whose bounds should be copied. 13080 * @param flags The adapter change flags that were passed into 13081 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, 13082 * List)}. 13083 * @return This {@link ItemHolderInfo} 13084 */ 13085 public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder, 13086 @AdapterChanges int flags) { 13087 final View view = holder.itemView; 13088 this.left = view.getLeft(); 13089 this.top = view.getTop(); 13090 this.right = view.getRight(); 13091 this.bottom = view.getBottom(); 13092 return this; 13093 } 13094 } 13095 } 13096 13097 @Override 13098 protected int getChildDrawingOrder(int childCount, int i) { 13099 if (mChildDrawingOrderCallback == null) { 13100 return super.getChildDrawingOrder(childCount, i); 13101 } else { 13102 return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i); 13103 } 13104 } 13105 13106 /** 13107 * A callback interface that can be used to alter the drawing order of RecyclerView children. 13108 * <p> 13109 * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case 13110 * that applies to that method also applies to this callback. For example, changing the drawing 13111 * order of two views will not have any effect if their elevation values are different since 13112 * elevation overrides the result of this callback. 13113 */ 13114 public interface ChildDrawingOrderCallback { 13115 /** 13116 * Returns the index of the child to draw for this iteration. Override this 13117 * if you want to change the drawing order of children. By default, it 13118 * returns i. 13119 * 13120 * @param i The current iteration. 13121 * @return The index of the child to draw this iteration. 13122 * 13123 * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) 13124 */ 13125 int onGetChildDrawingOrder(int childCount, int i); 13126 } 13127 13128 private NestedScrollingChildHelper getScrollingChildHelper() { 13129 if (mScrollingChildHelper == null) { 13130 mScrollingChildHelper = new NestedScrollingChildHelper(this); 13131 } 13132 return mScrollingChildHelper; 13133 } 13134} 13135