RecyclerView.java revision c76578ea4138aa224f8142a5de111ff38b79d9c3
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 android.util.AttributeSet; 41import android.util.Log; 42import android.util.SparseArray; 43import android.view.Display; 44import android.view.FocusFinder; 45import android.view.InputDevice; 46import android.view.MotionEvent; 47import android.view.VelocityTracker; 48import android.view.View; 49import android.view.ViewConfiguration; 50import android.view.ViewGroup; 51import android.view.ViewParent; 52import android.view.accessibility.AccessibilityEvent; 53import android.view.accessibility.AccessibilityManager; 54import android.view.animation.Interpolator; 55import android.widget.EdgeEffect; 56import android.widget.LinearLayout; 57import android.widget.OverScroller; 58 59import androidx.annotation.CallSuper; 60import androidx.annotation.IntDef; 61import androidx.annotation.NonNull; 62import androidx.annotation.Nullable; 63import androidx.annotation.Px; 64import androidx.annotation.RestrictTo; 65import androidx.annotation.VisibleForTesting; 66import androidx.core.os.TraceCompat; 67import androidx.core.util.Preconditions; 68import androidx.core.view.InputDeviceCompat; 69import androidx.core.view.MotionEventCompat; 70import androidx.core.view.NestedScrollingChild2; 71import androidx.core.view.NestedScrollingChildHelper; 72import androidx.core.view.ScrollingView; 73import androidx.core.view.ViewCompat; 74import androidx.core.view.ViewConfigurationCompat; 75import androidx.core.view.accessibility.AccessibilityEventCompat; 76import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 77import androidx.core.widget.EdgeEffectCompat; 78import androidx.customview.view.AbsSavedState; 79import androidx.recyclerview.R; 80import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo; 81import androidx.viewpager.widget.ViewPager; 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/** 94 * A flexible view for providing a limited window into a large data set. 95 * 96 * <h3>Glossary of terms:</h3> 97 * 98 * <ul> 99 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 100 * that represent items in a data set.</li> 101 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 102 * <li><em>Index:</em> The index of an attached child view as used in a call to 103 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 104 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 105 * to a <em>position</em> within the adapter.</li> 106 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 107 * position may be placed in a cache for later reuse to display the same type of data again 108 * later. This can drastically improve performance by skipping initial layout inflation 109 * or construction.</li> 110 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 111 * state during layout. Scrap views may be reused without becoming fully detached 112 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 113 * by the adapter if the view was considered <em>dirty</em>.</li> 114 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 115 * being displayed.</li> 116 * </ul> 117 * 118 * <h4>Positions in RecyclerView:</h4> 119 * <p> 120 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and 121 * {@link LayoutManager} to be able to detect data set changes in batches during a layout 122 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. 123 * It also helps with performance because all view bindings happen at the same time and unnecessary 124 * bindings are avoided. 125 * <p> 126 * For this reason, there are two types of <code>position</code> related methods in RecyclerView: 127 * <ul> 128 * <li>layout position: Position of an item in the latest layout calculation. This is the 129 * position from the LayoutManager's perspective.</li> 130 * <li>adapter position: Position of an item in the adapter. This is the position from 131 * the Adapter's perspective.</li> 132 * </ul> 133 * <p> 134 * These two positions are the same except the time between dispatching <code>adapter.notify* 135 * </code> events and calculating the updated layout. 136 * <p> 137 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest 138 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, 139 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the 140 * last layout calculation. You can rely on these positions to be consistent with what user is 141 * currently seeing on the screen. For example, if you have a list of items on the screen and user 142 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user 143 * is seeing. 144 * <p> 145 * The other set of position related methods are in the form of 146 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()}, 147 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to 148 * work with up-to-date adapter positions even if they may not have been reflected to layout yet. 149 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use 150 * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate 151 * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has 152 * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or 153 * <code>null</code> results from these methods. 154 * <p> 155 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when 156 * writing an {@link Adapter}, you probably want to use adapter positions. 157 * 158 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_layoutManager 159 */ 160public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 { 161 162 static final String TAG = "RecyclerView"; 163 164 static final boolean DEBUG = false; 165 166 static final boolean VERBOSE_TRACING = false; 167 168 private static final int[] NESTED_SCROLLING_ATTRS = 169 {16843830 /* android.R.attr.nestedScrollingEnabled */}; 170 171 private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding}; 172 173 /** 174 * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if 175 * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by 176 * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler 177 * recursively traverses itemView and invalidates display list for each ViewGroup that matches 178 * this criteria. 179 */ 180 static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 181 || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; 182 /** 183 * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, 184 * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to 185 * 0 when mode is unspecified. 186 */ 187 static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; 188 189 static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16; 190 191 /** 192 * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to 193 * RenderThread but before the next frame begins. We schedule prefetch work in this window. 194 */ 195 private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; 196 197 /** 198 * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction. 199 * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT. 200 */ 201 private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15; 202 203 /** 204 * on API 15-, a focused child can still be considered a focused child of RV even after 205 * it's being removed or its focusable flag is set to false. This is because when this focused 206 * child is detached, the reference to this child is not removed in clearFocus. API 16 and above 207 * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus 208 * to request focus on a new child, which will clear the focus on the old (detached) child as a 209 * side-effect. 210 */ 211 private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15; 212 213 static final boolean DISPATCH_TEMP_DETACH = false; 214 215 /** @hide */ 216 @RestrictTo(LIBRARY_GROUP) 217 @IntDef({HORIZONTAL, VERTICAL}) 218 @Retention(RetentionPolicy.SOURCE) 219 public @interface Orientation {} 220 221 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 222 public static final int VERTICAL = LinearLayout.VERTICAL; 223 224 static final int DEFAULT_ORIENTATION = VERTICAL; 225 public static final int NO_POSITION = -1; 226 public static final long NO_ID = -1; 227 public static final int INVALID_TYPE = -1; 228 229 /** 230 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 231 * that the RecyclerView should use the standard touch slop for smooth, 232 * continuous scrolling. 233 */ 234 public static final int TOUCH_SLOP_DEFAULT = 0; 235 236 /** 237 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 238 * that the RecyclerView should use the standard touch slop for scrolling 239 * widgets that snap to a page or other coarse-grained barrier. 240 */ 241 public static final int TOUCH_SLOP_PAGING = 1; 242 243 static final int MAX_SCROLL_DURATION = 2000; 244 245 /** 246 * RecyclerView is calculating a scroll. 247 * If there are too many of these in Systrace, some Views inside RecyclerView might be causing 248 * it. Try to avoid using EditText, focusable views or handle them with care. 249 */ 250 static final String TRACE_SCROLL_TAG = "RV Scroll"; 251 252 /** 253 * OnLayout has been called by the View system. 254 * If this shows up too many times in Systrace, make sure the children of RecyclerView do not 255 * update themselves directly. This will cause a full re-layout but when it happens via the 256 * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation. 257 */ 258 private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout"; 259 260 /** 261 * NotifyDataSetChanged or equal has been called. 262 * If this is taking a long time, try sending granular notify adapter changes instead of just 263 * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter 264 * might help. 265 */ 266 private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate"; 267 268 /** 269 * RecyclerView is doing a layout for partial adapter updates (we know what has changed) 270 * If this is taking a long time, you may have dispatched too many Adapter updates causing too 271 * many Views being rebind. Make sure all are necessary and also prefer using notify*Range 272 * methods. 273 */ 274 private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate"; 275 276 /** 277 * RecyclerView is rebinding a View. 278 * If this is taking a lot of time, consider optimizing your layout or make sure you are not 279 * doing extra operations in onBindViewHolder call. 280 */ 281 static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; 282 283 /** 284 * RecyclerView is attempting to pre-populate off screen views. 285 */ 286 static final String TRACE_PREFETCH_TAG = "RV Prefetch"; 287 288 /** 289 * RecyclerView is attempting to pre-populate off screen itemviews within an off screen 290 * RecyclerView. 291 */ 292 static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch"; 293 294 /** 295 * RecyclerView is creating a new View. 296 * If too many of these present in Systrace: 297 * - There might be a problem in Recycling (e.g. custom Animations that set transient state and 298 * prevent recycling or ItemAnimator not implementing the contract properly. ({@link 299 * > Adapter#onFailedToRecycleView(ViewHolder)}) 300 * 301 * - There might be too many item view types. 302 * > Try merging them 303 * 304 * - There might be too many itemChange animations and not enough space in RecyclerPool. 305 * >Try increasing your pool size and item cache size. 306 */ 307 static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; 308 private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = 309 new Class[]{Context.class, AttributeSet.class, int.class, int.class}; 310 311 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 312 313 final Recycler mRecycler = new Recycler(); 314 315 private SavedState mPendingSavedState; 316 317 /** 318 * Handles adapter updates 319 */ 320 AdapterHelper mAdapterHelper; 321 322 /** 323 * Handles abstraction between LayoutManager children and RecyclerView children 324 */ 325 ChildHelper mChildHelper; 326 327 /** 328 * Keeps data about views to be used for animations 329 */ 330 final ViewInfoStore mViewInfoStore = new ViewInfoStore(); 331 332 /** 333 * Prior to L, there is no way to query this variable which is why we override the setter and 334 * track it here. 335 */ 336 boolean mClipToPadding; 337 338 /** 339 * Note: this Runnable is only ever posted if: 340 * 1) We've been through first layout 341 * 2) We know we have a fixed size (mHasFixedSize) 342 * 3) We're attached 343 */ 344 final Runnable mUpdateChildViewsRunnable = new Runnable() { 345 @Override 346 public void run() { 347 if (!mFirstLayoutComplete || isLayoutRequested()) { 348 // a layout request will happen, we should not do layout here. 349 return; 350 } 351 if (!mIsAttached) { 352 requestLayout(); 353 // if we are not attached yet, mark us as requiring layout and skip 354 return; 355 } 356 if (mLayoutFrozen) { 357 mLayoutWasDefered = true; 358 return; //we'll process updates when ice age ends. 359 } 360 consumePendingUpdateOperations(); 361 } 362 }; 363 364 final Rect mTempRect = new Rect(); 365 private final Rect mTempRect2 = new Rect(); 366 final RectF mTempRectF = new RectF(); 367 Adapter mAdapter; 368 @VisibleForTesting LayoutManager mLayout; 369 RecyclerListener mRecyclerListener; 370 final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>(); 371 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 372 new ArrayList<>(); 373 private OnItemTouchListener mActiveOnItemTouchListener; 374 boolean mIsAttached; 375 boolean mHasFixedSize; 376 boolean mEnableFastScroller; 377 @VisibleForTesting boolean mFirstLayoutComplete; 378 379 /** 380 * The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of 381 * calls to {@link #startInterceptRequestLayout()} - number of calls to 382 * {@link #stopInterceptRequestLayout(boolean)} . This is used to signal whether we 383 * should defer layout operations caused by layout requests from children of 384 * {@link RecyclerView}. 385 */ 386 private int mInterceptRequestLayoutDepth = 0; 387 388 /** 389 * True if a call to requestLayout was intercepted and prevented from executing like normal and 390 * we plan on continuing with normal execution later. 391 */ 392 boolean mLayoutWasDefered; 393 394 boolean mLayoutFrozen; 395 private boolean mIgnoreMotionEventTillDown; 396 397 // binary OR of change events that were eaten during a layout or scroll. 398 private int mEatenAccessibilityChangeFlags; 399 boolean mAdapterUpdateDuringMeasure; 400 401 private final AccessibilityManager mAccessibilityManager; 402 private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners; 403 404 /** 405 * True after an event occurs that signals that the entire data set has changed. In that case, 406 * we cannot run any animations since we don't know what happened until layout. 407 * 408 * Attached items are invalid until next layout, at which point layout will animate/replace 409 * items as necessary, building up content from the (effectively) new adapter from scratch. 410 * 411 * Cached items must be discarded when setting this to true, so that the cache may be freely 412 * used by prefetching until the next layout occurs. 413 * 414 * @see #processDataSetCompletelyChanged(boolean) 415 */ 416 boolean mDataSetHasChangedAfterLayout = false; 417 418 /** 419 * True after the data set has completely changed and 420 * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent 421 * measure/layout. 422 * 423 * @see #processDataSetCompletelyChanged(boolean) 424 */ 425 boolean mDispatchItemsChangedEvent = false; 426 427 /** 428 * This variable is incremented during a dispatchLayout and/or scroll. 429 * Some methods should not be called during these periods (e.g. adapter data change). 430 * Doing so will create hard to find bugs so we better check it and throw an exception. 431 * 432 * @see #assertInLayoutOrScroll(String) 433 * @see #assertNotInLayoutOrScroll(String) 434 */ 435 private int mLayoutOrScrollCounter = 0; 436 437 /** 438 * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception 439 * (for API compatibility). 440 * <p> 441 * It is a bad practice for a developer to update the data in a scroll callback since it is 442 * potentially called during a layout. 443 */ 444 private int mDispatchScrollCounter = 0; 445 446 @NonNull 447 private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory(); 448 private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 449 450 ItemAnimator mItemAnimator = new DefaultItemAnimator(); 451 452 private static final int INVALID_POINTER = -1; 453 454 /** 455 * The RecyclerView is not currently scrolling. 456 * @see #getScrollState() 457 */ 458 public static final int SCROLL_STATE_IDLE = 0; 459 460 /** 461 * The RecyclerView is currently being dragged by outside input such as user touch input. 462 * @see #getScrollState() 463 */ 464 public static final int SCROLL_STATE_DRAGGING = 1; 465 466 /** 467 * The RecyclerView is currently animating to a final position while not under 468 * outside control. 469 * @see #getScrollState() 470 */ 471 public static final int SCROLL_STATE_SETTLING = 2; 472 473 static final long FOREVER_NS = Long.MAX_VALUE; 474 475 // Touch/scrolling handling 476 477 private int mScrollState = SCROLL_STATE_IDLE; 478 private int mScrollPointerId = INVALID_POINTER; 479 private VelocityTracker mVelocityTracker; 480 private int mInitialTouchX; 481 private int mInitialTouchY; 482 private int mLastTouchX; 483 private int mLastTouchY; 484 private int mTouchSlop; 485 private OnFlingListener mOnFlingListener; 486 private final int mMinFlingVelocity; 487 private final int mMaxFlingVelocity; 488 489 // This value is used when handling rotary encoder generic motion events. 490 private float mScaledHorizontalScrollFactor = Float.MIN_VALUE; 491 private float mScaledVerticalScrollFactor = Float.MIN_VALUE; 492 493 private boolean mPreserveFocusAfterLayout = true; 494 495 final ViewFlinger mViewFlinger = new ViewFlinger(); 496 497 GapWorker mGapWorker; 498 GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = 499 ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; 500 501 final State mState = new State(); 502 503 private OnScrollListener mScrollListener; 504 private List<OnScrollListener> mScrollListeners; 505 506 // For use in item animations 507 boolean mItemsAddedOrRemoved = false; 508 boolean mItemsChanged = false; 509 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = 510 new ItemAnimatorRestoreListener(); 511 boolean mPostedAnimatorRunner = false; 512 RecyclerViewAccessibilityDelegate mAccessibilityDelegate; 513 private ChildDrawingOrderCallback mChildDrawingOrderCallback; 514 515 // simple array to keep min and max child position during a layout calculation 516 // preserved not to create a new one in each layout pass 517 private final int[] mMinMaxLayoutPositions = new int[2]; 518 519 private NestedScrollingChildHelper mScrollingChildHelper; 520 private final int[] mScrollOffset = new int[2]; 521 private final int[] mScrollConsumed = new int[2]; 522 private final int[] mNestedOffsets = new int[2]; 523 524 /** 525 * Reusable int array for use in calls to {@link #scrollStep(int, int, int[])} so that the 526 * method may mutate it to "return" 2 ints. 527 */ 528 private final int[] mScrollStepConsumed = new int[2]; 529 530 /** 531 * These are views that had their a11y importance changed during a layout. We defer these events 532 * until the end of the layout because a11y service may make sync calls back to the RV while 533 * the View's state is undefined. 534 */ 535 @VisibleForTesting 536 final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList<>(); 537 538 private Runnable mItemAnimatorRunner = new Runnable() { 539 @Override 540 public void run() { 541 if (mItemAnimator != null) { 542 mItemAnimator.runPendingAnimations(); 543 } 544 mPostedAnimatorRunner = false; 545 } 546 }; 547 548 static final Interpolator sQuinticInterpolator = new Interpolator() { 549 @Override 550 public float getInterpolation(float t) { 551 t -= 1.0f; 552 return t * t * t * t * t + 1.0f; 553 } 554 }; 555 556 /** 557 * The callback to convert view info diffs into animations. 558 */ 559 private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = 560 new ViewInfoStore.ProcessCallback() { 561 @Override 562 public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, 563 @Nullable ItemHolderInfo postInfo) { 564 mRecycler.unscrapView(viewHolder); 565 animateDisappearance(viewHolder, info, postInfo); 566 } 567 @Override 568 public void processAppeared(ViewHolder viewHolder, 569 ItemHolderInfo preInfo, ItemHolderInfo info) { 570 animateAppearance(viewHolder, preInfo, info); 571 } 572 573 @Override 574 public void processPersistent(ViewHolder viewHolder, 575 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 576 viewHolder.setIsRecyclable(false); 577 if (mDataSetHasChangedAfterLayout) { 578 // since it was rebound, use change instead as we'll be mapping them from 579 // stable ids. If stable ids were false, we would not be running any 580 // animations 581 if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, 582 postInfo)) { 583 postAnimationRunner(); 584 } 585 } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { 586 postAnimationRunner(); 587 } 588 } 589 @Override 590 public void unused(ViewHolder viewHolder) { 591 mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); 592 } 593 }; 594 595 public RecyclerView(@NonNull Context context) { 596 this(context, null); 597 } 598 599 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 600 this(context, attrs, 0); 601 } 602 603 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 604 super(context, attrs, defStyle); 605 if (attrs != null) { 606 TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0); 607 mClipToPadding = a.getBoolean(0, true); 608 a.recycle(); 609 } else { 610 mClipToPadding = true; 611 } 612 setScrollContainer(true); 613 setFocusableInTouchMode(true); 614 615 final ViewConfiguration vc = ViewConfiguration.get(context); 616 mTouchSlop = vc.getScaledTouchSlop(); 617 mScaledHorizontalScrollFactor = 618 ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); 619 mScaledVerticalScrollFactor = 620 ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); 621 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 622 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 623 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 624 625 mItemAnimator.setListener(mItemAnimatorListener); 626 initAdapterManager(); 627 initChildrenHelper(); 628 // If not explicitly specified this view is important for accessibility. 629 if (ViewCompat.getImportantForAccessibility(this) 630 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 631 ViewCompat.setImportantForAccessibility(this, 632 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 633 } 634 mAccessibilityManager = (AccessibilityManager) getContext() 635 .getSystemService(Context.ACCESSIBILITY_SERVICE); 636 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 637 // Create the layoutManager if specified. 638 639 boolean nestedScrollingEnabled = true; 640 641 if (attrs != null) { 642 int defStyleRes = 0; 643 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 644 defStyle, defStyleRes); 645 String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); 646 int descendantFocusability = a.getInt( 647 R.styleable.RecyclerView_android_descendantFocusability, -1); 648 if (descendantFocusability == -1) { 649 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 650 } 651 mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); 652 if (mEnableFastScroller) { 653 StateListDrawable verticalThumbDrawable = (StateListDrawable) a 654 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); 655 Drawable verticalTrackDrawable = a 656 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); 657 StateListDrawable horizontalThumbDrawable = (StateListDrawable) a 658 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); 659 Drawable horizontalTrackDrawable = a 660 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); 661 initFastScroller(verticalThumbDrawable, verticalTrackDrawable, 662 horizontalThumbDrawable, horizontalTrackDrawable); 663 } 664 a.recycle(); 665 createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); 666 667 if (Build.VERSION.SDK_INT >= 21) { 668 a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, 669 defStyle, defStyleRes); 670 nestedScrollingEnabled = a.getBoolean(0, true); 671 a.recycle(); 672 } 673 } else { 674 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 675 } 676 677 // Re-set whether nested scrolling is enabled so that it is set on all API levels 678 setNestedScrollingEnabled(nestedScrollingEnabled); 679 } 680 681 /** 682 * Label appended to all public exception strings, used to help find which RV in an app is 683 * hitting an exception. 684 */ 685 String exceptionLabel() { 686 return " " + super.toString() 687 + ", adapter:" + mAdapter 688 + ", layout:" + mLayout 689 + ", context:" + getContext(); 690 } 691 692 /** 693 * Returns the accessibility delegate compatibility implementation used by the RecyclerView. 694 * @return An instance of AccessibilityDelegateCompat used by RecyclerView 695 */ 696 @Nullable 697 public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { 698 return mAccessibilityDelegate; 699 } 700 701 /** 702 * Sets the accessibility delegate compatibility implementation used by RecyclerView. 703 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. 704 */ 705 public void setAccessibilityDelegateCompat( 706 @Nullable RecyclerViewAccessibilityDelegate accessibilityDelegate) { 707 mAccessibilityDelegate = accessibilityDelegate; 708 ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); 709 } 710 711 /** 712 * Instantiate and set a LayoutManager, if specified in the attributes. 713 */ 714 private void createLayoutManager(Context context, String className, AttributeSet attrs, 715 int defStyleAttr, int defStyleRes) { 716 if (className != null) { 717 className = className.trim(); 718 if (!className.isEmpty()) { 719 className = getFullClassName(context, className); 720 try { 721 ClassLoader classLoader; 722 if (isInEditMode()) { 723 // Stupid layoutlib cannot handle simple class loaders. 724 classLoader = this.getClass().getClassLoader(); 725 } else { 726 classLoader = context.getClassLoader(); 727 } 728 Class<? extends LayoutManager> layoutManagerClass = 729 classLoader.loadClass(className).asSubclass(LayoutManager.class); 730 Constructor<? extends LayoutManager> constructor; 731 Object[] constructorArgs = null; 732 try { 733 constructor = layoutManagerClass 734 .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE); 735 constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes}; 736 } catch (NoSuchMethodException e) { 737 try { 738 constructor = layoutManagerClass.getConstructor(); 739 } catch (NoSuchMethodException e1) { 740 e1.initCause(e); 741 throw new IllegalStateException(attrs.getPositionDescription() 742 + ": Error creating LayoutManager " + className, e1); 743 } 744 } 745 constructor.setAccessible(true); 746 setLayoutManager(constructor.newInstance(constructorArgs)); 747 } catch (ClassNotFoundException e) { 748 throw new IllegalStateException(attrs.getPositionDescription() 749 + ": Unable to find LayoutManager " + className, e); 750 } catch (InvocationTargetException e) { 751 throw new IllegalStateException(attrs.getPositionDescription() 752 + ": Could not instantiate the LayoutManager: " + className, e); 753 } catch (InstantiationException e) { 754 throw new IllegalStateException(attrs.getPositionDescription() 755 + ": Could not instantiate the LayoutManager: " + className, e); 756 } catch (IllegalAccessException e) { 757 throw new IllegalStateException(attrs.getPositionDescription() 758 + ": Cannot access non-public constructor " + className, e); 759 } catch (ClassCastException e) { 760 throw new IllegalStateException(attrs.getPositionDescription() 761 + ": Class is not a LayoutManager " + className, e); 762 } 763 } 764 } 765 } 766 767 private String getFullClassName(Context context, String className) { 768 if (className.charAt(0) == '.') { 769 return context.getPackageName() + className; 770 } 771 if (className.contains(".")) { 772 return className; 773 } 774 return RecyclerView.class.getPackage().getName() + '.' + className; 775 } 776 777 private void initChildrenHelper() { 778 mChildHelper = new ChildHelper(new ChildHelper.Callback() { 779 @Override 780 public int getChildCount() { 781 return RecyclerView.this.getChildCount(); 782 } 783 784 @Override 785 public void addView(View child, int index) { 786 if (VERBOSE_TRACING) { 787 TraceCompat.beginSection("RV addView"); 788 } 789 RecyclerView.this.addView(child, index); 790 if (VERBOSE_TRACING) { 791 TraceCompat.endSection(); 792 } 793 dispatchChildAttached(child); 794 } 795 796 @Override 797 public int indexOfChild(View view) { 798 return RecyclerView.this.indexOfChild(view); 799 } 800 801 @Override 802 public void removeViewAt(int index) { 803 final View child = RecyclerView.this.getChildAt(index); 804 if (child != null) { 805 dispatchChildDetached(child); 806 807 // Clear any android.view.animation.Animation that may prevent the item from 808 // detaching when being removed. If a child is re-added before the 809 // lazy detach occurs, it will receive invalid attach/detach sequencing. 810 child.clearAnimation(); 811 } 812 if (VERBOSE_TRACING) { 813 TraceCompat.beginSection("RV removeViewAt"); 814 } 815 RecyclerView.this.removeViewAt(index); 816 if (VERBOSE_TRACING) { 817 TraceCompat.endSection(); 818 } 819 } 820 821 @Override 822 public View getChildAt(int offset) { 823 return RecyclerView.this.getChildAt(offset); 824 } 825 826 @Override 827 public void removeAllViews() { 828 final int count = getChildCount(); 829 for (int i = 0; i < count; i++) { 830 View child = getChildAt(i); 831 dispatchChildDetached(child); 832 833 // Clear any android.view.animation.Animation that may prevent the item from 834 // detaching when being removed. If a child is re-added before the 835 // lazy detach occurs, it will receive invalid attach/detach sequencing. 836 child.clearAnimation(); 837 } 838 RecyclerView.this.removeAllViews(); 839 } 840 841 @Override 842 public ViewHolder getChildViewHolder(View view) { 843 return getChildViewHolderInt(view); 844 } 845 846 @Override 847 public void attachViewToParent(View child, int index, 848 ViewGroup.LayoutParams layoutParams) { 849 final ViewHolder vh = getChildViewHolderInt(child); 850 if (vh != null) { 851 if (!vh.isTmpDetached() && !vh.shouldIgnore()) { 852 throw new IllegalArgumentException("Called attach on a child which is not" 853 + " detached: " + vh + exceptionLabel()); 854 } 855 if (DEBUG) { 856 Log.d(TAG, "reAttach " + vh); 857 } 858 vh.clearTmpDetachFlag(); 859 } 860 RecyclerView.this.attachViewToParent(child, index, layoutParams); 861 } 862 863 @Override 864 public void detachViewFromParent(int offset) { 865 final View view = getChildAt(offset); 866 if (view != null) { 867 final ViewHolder vh = getChildViewHolderInt(view); 868 if (vh != null) { 869 if (vh.isTmpDetached() && !vh.shouldIgnore()) { 870 throw new IllegalArgumentException("called detach on an already" 871 + " detached child " + vh + exceptionLabel()); 872 } 873 if (DEBUG) { 874 Log.d(TAG, "tmpDetach " + vh); 875 } 876 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); 877 } 878 } 879 RecyclerView.this.detachViewFromParent(offset); 880 } 881 882 @Override 883 public void onEnteredHiddenState(View child) { 884 final ViewHolder vh = getChildViewHolderInt(child); 885 if (vh != null) { 886 vh.onEnteredHiddenState(RecyclerView.this); 887 } 888 } 889 890 @Override 891 public void onLeftHiddenState(View child) { 892 final ViewHolder vh = getChildViewHolderInt(child); 893 if (vh != null) { 894 vh.onLeftHiddenState(RecyclerView.this); 895 } 896 } 897 }); 898 } 899 900 void initAdapterManager() { 901 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { 902 @Override 903 public ViewHolder findViewHolder(int position) { 904 final ViewHolder vh = findViewHolderForPosition(position, true); 905 if (vh == null) { 906 return null; 907 } 908 // ensure it is not hidden because for adapter helper, the only thing matter is that 909 // LM thinks view is a child. 910 if (mChildHelper.isHidden(vh.itemView)) { 911 if (DEBUG) { 912 Log.d(TAG, "assuming view holder cannot be find because it is hidden"); 913 } 914 return null; 915 } 916 return vh; 917 } 918 919 @Override 920 public void offsetPositionsForRemovingInvisible(int start, int count) { 921 offsetPositionRecordsForRemove(start, count, true); 922 mItemsAddedOrRemoved = true; 923 mState.mDeletedInvisibleItemCountSincePreviousLayout += count; 924 } 925 926 @Override 927 public void offsetPositionsForRemovingLaidOutOrNewView( 928 int positionStart, int itemCount) { 929 offsetPositionRecordsForRemove(positionStart, itemCount, false); 930 mItemsAddedOrRemoved = true; 931 } 932 933 934 @Override 935 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { 936 viewRangeUpdate(positionStart, itemCount, payload); 937 mItemsChanged = true; 938 } 939 940 @Override 941 public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { 942 dispatchUpdate(op); 943 } 944 945 void dispatchUpdate(AdapterHelper.UpdateOp op) { 946 switch (op.cmd) { 947 case AdapterHelper.UpdateOp.ADD: 948 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); 949 break; 950 case AdapterHelper.UpdateOp.REMOVE: 951 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); 952 break; 953 case AdapterHelper.UpdateOp.UPDATE: 954 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, 955 op.payload); 956 break; 957 case AdapterHelper.UpdateOp.MOVE: 958 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); 959 break; 960 } 961 } 962 963 @Override 964 public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { 965 dispatchUpdate(op); 966 } 967 968 @Override 969 public void offsetPositionsForAdd(int positionStart, int itemCount) { 970 offsetPositionRecordsForInsert(positionStart, itemCount); 971 mItemsAddedOrRemoved = true; 972 } 973 974 @Override 975 public void offsetPositionsForMove(int from, int to) { 976 offsetPositionRecordsForMove(from, to); 977 // should we create mItemsMoved ? 978 mItemsAddedOrRemoved = true; 979 } 980 }); 981 } 982 983 /** 984 * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's 985 * size is not affected by the adapter contents. RecyclerView can still change its size based 986 * on other factors (e.g. its parent's size) but this size calculation cannot depend on the 987 * size of its children or contents of its adapter (except the number of items in the adapter). 988 * <p> 989 * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow 990 * RecyclerView to avoid invalidating the whole layout when its adapter contents change. 991 * 992 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 993 */ 994 public void setHasFixedSize(boolean hasFixedSize) { 995 mHasFixedSize = hasFixedSize; 996 } 997 998 /** 999 * @return true if the app has specified that changes in adapter content cannot change 1000 * the size of the RecyclerView itself. 1001 */ 1002 public boolean hasFixedSize() { 1003 return mHasFixedSize; 1004 } 1005 1006 @Override 1007 public void setClipToPadding(boolean clipToPadding) { 1008 if (clipToPadding != mClipToPadding) { 1009 invalidateGlows(); 1010 } 1011 mClipToPadding = clipToPadding; 1012 super.setClipToPadding(clipToPadding); 1013 if (mFirstLayoutComplete) { 1014 requestLayout(); 1015 } 1016 } 1017 1018 /** 1019 * Returns whether this RecyclerView will clip its children to its padding, and resize (but 1020 * not clip) any EdgeEffect to the padded region, if padding is present. 1021 * <p> 1022 * By default, children are clipped to the padding of their parent 1023 * RecyclerView. This clipping behavior is only enabled if padding is non-zero. 1024 * 1025 * @return true if this RecyclerView clips children to its padding and resizes (but doesn't 1026 * clip) any EdgeEffect to the padded region, false otherwise. 1027 * 1028 * @attr name android:clipToPadding 1029 */ 1030 @Override 1031 public boolean getClipToPadding() { 1032 return mClipToPadding; 1033 } 1034 1035 /** 1036 * Configure the scrolling touch slop for a specific use case. 1037 * 1038 * Set up the RecyclerView's scrolling motion threshold based on common usages. 1039 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. 1040 * 1041 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing 1042 * the intended usage of this RecyclerView 1043 */ 1044 public void setScrollingTouchSlop(int slopConstant) { 1045 final ViewConfiguration vc = ViewConfiguration.get(getContext()); 1046 switch (slopConstant) { 1047 default: 1048 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " 1049 + slopConstant + "; using default value"); 1050 // fall-through 1051 case TOUCH_SLOP_DEFAULT: 1052 mTouchSlop = vc.getScaledTouchSlop(); 1053 break; 1054 1055 case TOUCH_SLOP_PAGING: 1056 mTouchSlop = vc.getScaledPagingTouchSlop(); 1057 break; 1058 } 1059 } 1060 1061 /** 1062 * Swaps the current adapter with the provided one. It is similar to 1063 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same 1064 * {@link ViewHolder} and does not clear the RecycledViewPool. 1065 * <p> 1066 * Note that it still calls onAdapterChanged callbacks. 1067 * 1068 * @param adapter The new adapter to set, or null to set no adapter. 1069 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing 1070 * Views. If adapters have stable ids and/or you want to 1071 * animate the disappearing views, you may prefer to set 1072 * this to false. 1073 * @see #setAdapter(Adapter) 1074 */ 1075 public void swapAdapter(@Nullable Adapter adapter, boolean removeAndRecycleExistingViews) { 1076 // bail out if layout is frozen 1077 setLayoutFrozen(false); 1078 setAdapterInternal(adapter, true, removeAndRecycleExistingViews); 1079 processDataSetCompletelyChanged(true); 1080 requestLayout(); 1081 } 1082 /** 1083 * Set a new adapter to provide child views on demand. 1084 * <p> 1085 * When adapter is changed, all existing views are recycled back to the pool. If the pool has 1086 * only one adapter, it will be cleared. 1087 * 1088 * @param adapter The new adapter to set, or null to set no adapter. 1089 * @see #swapAdapter(Adapter, boolean) 1090 */ 1091 public void setAdapter(@Nullable Adapter adapter) { 1092 // bail out if layout is frozen 1093 setLayoutFrozen(false); 1094 setAdapterInternal(adapter, false, true); 1095 processDataSetCompletelyChanged(false); 1096 requestLayout(); 1097 } 1098 1099 /** 1100 * Removes and recycles all views - both those currently attached, and those in the Recycler. 1101 */ 1102 void removeAndRecycleViews() { 1103 // end all running animations 1104 if (mItemAnimator != null) { 1105 mItemAnimator.endAnimations(); 1106 } 1107 // Since animations are ended, mLayout.children should be equal to 1108 // recyclerView.children. This may not be true if item animator's end does not work as 1109 // expected. (e.g. not release children instantly). It is safer to use mLayout's child 1110 // count. 1111 if (mLayout != null) { 1112 mLayout.removeAndRecycleAllViews(mRecycler); 1113 mLayout.removeAndRecycleScrapInt(mRecycler); 1114 } 1115 // we should clear it here before adapters are swapped to ensure correct callbacks. 1116 mRecycler.clear(); 1117 } 1118 1119 /** 1120 * Replaces the current adapter with the new one and triggers listeners. 1121 * @param adapter The new adapter 1122 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and 1123 * item types with the current adapter (helps us avoid cache 1124 * invalidation). 1125 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If 1126 * compatibleWithPrevious is false, this parameter is ignored. 1127 */ 1128 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, 1129 boolean removeAndRecycleViews) { 1130 if (mAdapter != null) { 1131 mAdapter.unregisterAdapterDataObserver(mObserver); 1132 mAdapter.onDetachedFromRecyclerView(this); 1133 } 1134 if (!compatibleWithPrevious || removeAndRecycleViews) { 1135 removeAndRecycleViews(); 1136 } 1137 mAdapterHelper.reset(); 1138 final Adapter oldAdapter = mAdapter; 1139 mAdapter = adapter; 1140 if (adapter != null) { 1141 adapter.registerAdapterDataObserver(mObserver); 1142 adapter.onAttachedToRecyclerView(this); 1143 } 1144 if (mLayout != null) { 1145 mLayout.onAdapterChanged(oldAdapter, mAdapter); 1146 } 1147 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 1148 mState.mStructureChanged = true; 1149 } 1150 1151 /** 1152 * Retrieves the previously set adapter or null if no adapter is set. 1153 * 1154 * @return The previously set adapter 1155 * @see #setAdapter(Adapter) 1156 */ 1157 @Nullable 1158 public Adapter getAdapter() { 1159 return mAdapter; 1160 } 1161 1162 /** 1163 * Register a listener that will be notified whenever a child view is recycled. 1164 * 1165 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1166 * that a child view is no longer needed. If an application associates expensive 1167 * or heavyweight data with item views, this may be a good place to release 1168 * or free those resources.</p> 1169 * 1170 * @param listener Listener to register, or null to clear 1171 */ 1172 public void setRecyclerListener(@Nullable RecyclerListener listener) { 1173 mRecyclerListener = listener; 1174 } 1175 1176 /** 1177 * <p>Return the offset of the RecyclerView's text baseline from the its top 1178 * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, 1179 * this method returns -1.</p> 1180 * 1181 * @return the offset of the baseline within the RecyclerView's bounds or -1 1182 * if baseline alignment is not supported 1183 */ 1184 @Override 1185 public int getBaseline() { 1186 if (mLayout != null) { 1187 return mLayout.getBaseline(); 1188 } else { 1189 return super.getBaseline(); 1190 } 1191 } 1192 1193 /** 1194 * Register a listener that will be notified whenever a child view is attached to or detached 1195 * from RecyclerView. 1196 * 1197 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1198 * that a child view is no longer needed. If an application associates expensive 1199 * or heavyweight data with item views, this may be a good place to release 1200 * or free those resources.</p> 1201 * 1202 * @param listener Listener to register 1203 */ 1204 public void addOnChildAttachStateChangeListener( 1205 @NonNull OnChildAttachStateChangeListener listener) { 1206 if (mOnChildAttachStateListeners == null) { 1207 mOnChildAttachStateListeners = new ArrayList<>(); 1208 } 1209 mOnChildAttachStateListeners.add(listener); 1210 } 1211 1212 /** 1213 * Removes the provided listener from child attached state listeners list. 1214 * 1215 * @param listener Listener to unregister 1216 */ 1217 public void removeOnChildAttachStateChangeListener( 1218 @NonNull OnChildAttachStateChangeListener listener) { 1219 if (mOnChildAttachStateListeners == null) { 1220 return; 1221 } 1222 mOnChildAttachStateListeners.remove(listener); 1223 } 1224 1225 /** 1226 * Removes all listeners that were added via 1227 * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}. 1228 */ 1229 public void clearOnChildAttachStateChangeListeners() { 1230 if (mOnChildAttachStateListeners != null) { 1231 mOnChildAttachStateListeners.clear(); 1232 } 1233 } 1234 1235 /** 1236 * Set the {@link LayoutManager} that this RecyclerView will use. 1237 * 1238 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 1239 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 1240 * layout arrangements for child views. These arrangements are controlled by the 1241 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 1242 * 1243 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 1244 * 1245 * @param layout LayoutManager to use 1246 */ 1247 public void setLayoutManager(@Nullable LayoutManager layout) { 1248 if (layout == mLayout) { 1249 return; 1250 } 1251 stopScroll(); 1252 // TODO We should do this switch a dispatchLayout pass and animate children. There is a good 1253 // chance that LayoutManagers will re-use views. 1254 if (mLayout != null) { 1255 // end all running animations 1256 if (mItemAnimator != null) { 1257 mItemAnimator.endAnimations(); 1258 } 1259 mLayout.removeAndRecycleAllViews(mRecycler); 1260 mLayout.removeAndRecycleScrapInt(mRecycler); 1261 mRecycler.clear(); 1262 1263 if (mIsAttached) { 1264 mLayout.dispatchDetachedFromWindow(this, mRecycler); 1265 } 1266 mLayout.setRecyclerView(null); 1267 mLayout = null; 1268 } else { 1269 mRecycler.clear(); 1270 } 1271 // this is just a defensive measure for faulty item animators. 1272 mChildHelper.removeAllViewsUnfiltered(); 1273 mLayout = layout; 1274 if (layout != null) { 1275 if (layout.mRecyclerView != null) { 1276 throw new IllegalArgumentException("LayoutManager " + layout 1277 + " is already attached to a RecyclerView:" 1278 + layout.mRecyclerView.exceptionLabel()); 1279 } 1280 mLayout.setRecyclerView(this); 1281 if (mIsAttached) { 1282 mLayout.dispatchAttachedToWindow(this); 1283 } 1284 } 1285 mRecycler.updateViewCacheSize(); 1286 requestLayout(); 1287 } 1288 1289 /** 1290 * Set a {@link OnFlingListener} for this {@link RecyclerView}. 1291 * <p> 1292 * If the {@link OnFlingListener} is set then it will receive 1293 * calls to {@link #fling(int,int)} and will be able to intercept them. 1294 * 1295 * @param onFlingListener The {@link OnFlingListener} instance. 1296 */ 1297 public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { 1298 mOnFlingListener = onFlingListener; 1299 } 1300 1301 /** 1302 * Get the current {@link OnFlingListener} from this {@link RecyclerView}. 1303 * 1304 * @return The {@link OnFlingListener} instance currently set (can be null). 1305 */ 1306 @Nullable 1307 public OnFlingListener getOnFlingListener() { 1308 return mOnFlingListener; 1309 } 1310 1311 @Override 1312 protected Parcelable onSaveInstanceState() { 1313 SavedState state = new SavedState(super.onSaveInstanceState()); 1314 if (mPendingSavedState != null) { 1315 state.copyFrom(mPendingSavedState); 1316 } else if (mLayout != null) { 1317 state.mLayoutState = mLayout.onSaveInstanceState(); 1318 } else { 1319 state.mLayoutState = null; 1320 } 1321 1322 return state; 1323 } 1324 1325 @Override 1326 protected void onRestoreInstanceState(Parcelable state) { 1327 if (!(state instanceof SavedState)) { 1328 super.onRestoreInstanceState(state); 1329 return; 1330 } 1331 1332 mPendingSavedState = (SavedState) state; 1333 super.onRestoreInstanceState(mPendingSavedState.getSuperState()); 1334 if (mLayout != null && mPendingSavedState.mLayoutState != null) { 1335 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); 1336 } 1337 } 1338 1339 /** 1340 * Override to prevent freezing of any views created by the adapter. 1341 */ 1342 @Override 1343 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 1344 dispatchFreezeSelfOnly(container); 1345 } 1346 1347 /** 1348 * Override to prevent thawing of any views created by the adapter. 1349 */ 1350 @Override 1351 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 1352 dispatchThawSelfOnly(container); 1353 } 1354 1355 /** 1356 * Adds a view to the animatingViews list. 1357 * mAnimatingViews holds the child views that are currently being kept around 1358 * purely for the purpose of being animated out of view. They are drawn as a regular 1359 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager 1360 * as they are managed separately from the regular child views. 1361 * @param viewHolder The ViewHolder to be removed 1362 */ 1363 private void addAnimatingView(ViewHolder viewHolder) { 1364 final View view = viewHolder.itemView; 1365 final boolean alreadyParented = view.getParent() == this; 1366 mRecycler.unscrapView(getChildViewHolder(view)); 1367 if (viewHolder.isTmpDetached()) { 1368 // re-attach 1369 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); 1370 } else if (!alreadyParented) { 1371 mChildHelper.addView(view, true); 1372 } else { 1373 mChildHelper.hide(view); 1374 } 1375 } 1376 1377 /** 1378 * Removes a view from the animatingViews list. 1379 * @param view The view to be removed 1380 * @see #addAnimatingView(RecyclerView.ViewHolder) 1381 * @return true if an animating view is removed 1382 */ 1383 boolean removeAnimatingView(View view) { 1384 startInterceptRequestLayout(); 1385 final boolean removed = mChildHelper.removeViewIfHidden(view); 1386 if (removed) { 1387 final ViewHolder viewHolder = getChildViewHolderInt(view); 1388 mRecycler.unscrapView(viewHolder); 1389 mRecycler.recycleViewHolderInternal(viewHolder); 1390 if (DEBUG) { 1391 Log.d(TAG, "after removing animated view: " + view + ", " + this); 1392 } 1393 } 1394 // only clear request eaten flag if we removed the view. 1395 stopInterceptRequestLayout(!removed); 1396 return removed; 1397 } 1398 1399 /** 1400 * Return the {@link LayoutManager} currently responsible for 1401 * layout policy for this RecyclerView. 1402 * 1403 * @return The currently bound LayoutManager 1404 */ 1405 @Nullable 1406 public LayoutManager getLayoutManager() { 1407 return mLayout; 1408 } 1409 1410 /** 1411 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 1412 * if no pool is set for this view a new one will be created. See 1413 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 1414 * 1415 * @return The pool used to store recycled item views for reuse. 1416 * @see #setRecycledViewPool(RecycledViewPool) 1417 */ 1418 @NonNull 1419 public RecycledViewPool getRecycledViewPool() { 1420 return mRecycler.getRecycledViewPool(); 1421 } 1422 1423 /** 1424 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 1425 * This can be useful if you have multiple RecyclerViews with adapters that use the same 1426 * view types, for example if you have several data sets with the same kinds of item views 1427 * displayed by a {@link ViewPager ViewPager}. 1428 * 1429 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 1430 */ 1431 public void setRecycledViewPool(@Nullable RecycledViewPool pool) { 1432 mRecycler.setRecycledViewPool(pool); 1433 } 1434 1435 /** 1436 * Sets a new {@link ViewCacheExtension} to be used by the Recycler. 1437 * 1438 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. 1439 * 1440 * @see ViewCacheExtension#getViewForPositionAndType(Recycler, int, int) 1441 */ 1442 public void setViewCacheExtension(@Nullable ViewCacheExtension extension) { 1443 mRecycler.setViewCacheExtension(extension); 1444 } 1445 1446 /** 1447 * Set the number of offscreen views to retain before adding them to the potentially shared 1448 * {@link #getRecycledViewPool() recycled view pool}. 1449 * 1450 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 1451 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 1452 * to rebind them.</p> 1453 * 1454 * @param size Number of views to cache offscreen before returning them to the general 1455 * recycled view pool 1456 */ 1457 public void setItemViewCacheSize(int size) { 1458 mRecycler.setViewCacheSize(size); 1459 } 1460 1461 /** 1462 * Return the current scrolling state of the RecyclerView. 1463 * 1464 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 1465 * {@link #SCROLL_STATE_SETTLING} 1466 */ 1467 public int getScrollState() { 1468 return mScrollState; 1469 } 1470 1471 void setScrollState(int state) { 1472 if (state == mScrollState) { 1473 return; 1474 } 1475 if (DEBUG) { 1476 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, 1477 new Exception()); 1478 } 1479 mScrollState = state; 1480 if (state != SCROLL_STATE_SETTLING) { 1481 stopScrollersInternal(); 1482 } 1483 dispatchOnScrollStateChanged(state); 1484 } 1485 1486 /** 1487 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1488 * affect both measurement and drawing of individual item views. 1489 * 1490 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1491 * be run/queried/drawn first for their effects on item views. Padding added to views 1492 * will be nested; a padding added by an earlier decoration will mean further 1493 * item decorations in the list will be asked to draw/pad within the previous decoration's 1494 * given area.</p> 1495 * 1496 * @param decor Decoration to add 1497 * @param index Position in the decoration chain to insert this decoration at. If this value 1498 * is negative the decoration will be added at the end. 1499 */ 1500 public void addItemDecoration(@NonNull ItemDecoration decor, int index) { 1501 if (mLayout != null) { 1502 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" 1503 + " layout"); 1504 } 1505 if (mItemDecorations.isEmpty()) { 1506 setWillNotDraw(false); 1507 } 1508 if (index < 0) { 1509 mItemDecorations.add(decor); 1510 } else { 1511 mItemDecorations.add(index, decor); 1512 } 1513 markItemDecorInsetsDirty(); 1514 requestLayout(); 1515 } 1516 1517 /** 1518 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1519 * affect both measurement and drawing of individual item views. 1520 * 1521 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1522 * be run/queried/drawn first for their effects on item views. Padding added to views 1523 * will be nested; a padding added by an earlier decoration will mean further 1524 * item decorations in the list will be asked to draw/pad within the previous decoration's 1525 * given area.</p> 1526 * 1527 * @param decor Decoration to add 1528 */ 1529 public void addItemDecoration(@NonNull ItemDecoration decor) { 1530 addItemDecoration(decor, -1); 1531 } 1532 1533 /** 1534 * Returns an {@link ItemDecoration} previously added to this RecyclerView. 1535 * 1536 * @param index The index position of the desired ItemDecoration. 1537 * @return the ItemDecoration at index position 1538 * @throws IndexOutOfBoundsException on invalid index 1539 */ 1540 @NonNull 1541 public ItemDecoration getItemDecorationAt(int index) { 1542 final int size = getItemDecorationCount(); 1543 if (index < 0 || index >= size) { 1544 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1545 } 1546 1547 return mItemDecorations.get(index); 1548 } 1549 1550 /** 1551 * Returns the number of {@link ItemDecoration} currently added to this RecyclerView. 1552 * 1553 * @return number of ItemDecorations currently added added to this RecyclerView. 1554 */ 1555 public int getItemDecorationCount() { 1556 return mItemDecorations.size(); 1557 } 1558 1559 /** 1560 * Removes the {@link ItemDecoration} associated with the supplied index position. 1561 * 1562 * @param index The index position of the ItemDecoration to be removed. 1563 */ 1564 public void removeItemDecorationAt(int index) { 1565 final int size = getItemDecorationCount(); 1566 if (index < 0 || index >= size) { 1567 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1568 } 1569 1570 removeItemDecoration(getItemDecorationAt(index)); 1571 } 1572 1573 /** 1574 * Remove an {@link ItemDecoration} from this RecyclerView. 1575 * 1576 * <p>The given decoration will no longer impact the measurement and drawing of 1577 * item views.</p> 1578 * 1579 * @param decor Decoration to remove 1580 * @see #addItemDecoration(ItemDecoration) 1581 */ 1582 public void removeItemDecoration(@NonNull ItemDecoration decor) { 1583 if (mLayout != null) { 1584 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" 1585 + " layout"); 1586 } 1587 mItemDecorations.remove(decor); 1588 if (mItemDecorations.isEmpty()) { 1589 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 1590 } 1591 markItemDecorInsetsDirty(); 1592 requestLayout(); 1593 } 1594 1595 /** 1596 * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children. 1597 * <p> 1598 * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will 1599 * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be 1600 * true if childDrawingOrderCallback is not null, false otherwise. 1601 * <p> 1602 * Note that child drawing order may be overridden by View's elevation. 1603 * 1604 * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing 1605 * system. 1606 */ 1607 public void setChildDrawingOrderCallback( 1608 @Nullable ChildDrawingOrderCallback childDrawingOrderCallback) { 1609 if (childDrawingOrderCallback == mChildDrawingOrderCallback) { 1610 return; 1611 } 1612 mChildDrawingOrderCallback = childDrawingOrderCallback; 1613 setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null); 1614 } 1615 1616 /** 1617 * Set a listener that will be notified of any changes in scroll state or position. 1618 * 1619 * @param listener Listener to set or null to clear 1620 * 1621 * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and 1622 * {@link #removeOnScrollListener(OnScrollListener)} 1623 */ 1624 @Deprecated 1625 public void setOnScrollListener(@Nullable OnScrollListener listener) { 1626 mScrollListener = listener; 1627 } 1628 1629 /** 1630 * Add a listener that will be notified of any changes in scroll state or position. 1631 * 1632 * <p>Components that add a listener should take care to remove it when finished. 1633 * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} 1634 * to remove all attached listeners.</p> 1635 * 1636 * @param listener listener to set 1637 */ 1638 public void addOnScrollListener(@NonNull OnScrollListener listener) { 1639 if (mScrollListeners == null) { 1640 mScrollListeners = new ArrayList<>(); 1641 } 1642 mScrollListeners.add(listener); 1643 } 1644 1645 /** 1646 * Remove a listener that was notified of any changes in scroll state or position. 1647 * 1648 * @param listener listener to set or null to clear 1649 */ 1650 public void removeOnScrollListener(@NonNull OnScrollListener listener) { 1651 if (mScrollListeners != null) { 1652 mScrollListeners.remove(listener); 1653 } 1654 } 1655 1656 /** 1657 * Remove all secondary listener that were notified of any changes in scroll state or position. 1658 */ 1659 public void clearOnScrollListeners() { 1660 if (mScrollListeners != null) { 1661 mScrollListeners.clear(); 1662 } 1663 } 1664 1665 /** 1666 * Convenience method to scroll to a certain position. 1667 * 1668 * RecyclerView does not implement scrolling logic, rather forwards the call to 1669 * {@link RecyclerView.LayoutManager#scrollToPosition(int)} 1670 * @param position Scroll to this adapter position 1671 * @see RecyclerView.LayoutManager#scrollToPosition(int) 1672 */ 1673 public void scrollToPosition(int position) { 1674 if (mLayoutFrozen) { 1675 return; 1676 } 1677 stopScroll(); 1678 if (mLayout == null) { 1679 Log.e(TAG, "Cannot scroll to position a LayoutManager set. " 1680 + "Call setLayoutManager with a non-null argument."); 1681 return; 1682 } 1683 mLayout.scrollToPosition(position); 1684 awakenScrollBars(); 1685 } 1686 1687 void jumpToPositionForSmoothScroller(int position) { 1688 if (mLayout == null) { 1689 return; 1690 } 1691 mLayout.scrollToPosition(position); 1692 awakenScrollBars(); 1693 } 1694 1695 /** 1696 * Starts a smooth scroll to an adapter position. 1697 * <p> 1698 * To support smooth scrolling, you must override 1699 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a 1700 * {@link SmoothScroller}. 1701 * <p> 1702 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to 1703 * provide a custom smooth scroll logic, override 1704 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your 1705 * LayoutManager. 1706 * 1707 * @param position The adapter position to scroll to 1708 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) 1709 */ 1710 public void smoothScrollToPosition(int position) { 1711 if (mLayoutFrozen) { 1712 return; 1713 } 1714 if (mLayout == null) { 1715 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 1716 + "Call setLayoutManager with a non-null argument."); 1717 return; 1718 } 1719 mLayout.smoothScrollToPosition(this, mState, position); 1720 } 1721 1722 @Override 1723 public void scrollTo(int x, int y) { 1724 Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " 1725 + "Use scrollToPosition instead"); 1726 } 1727 1728 @Override 1729 public void scrollBy(int x, int y) { 1730 if (mLayout == null) { 1731 Log.e(TAG, "Cannot scroll without a LayoutManager set. " 1732 + "Call setLayoutManager with a non-null argument."); 1733 return; 1734 } 1735 if (mLayoutFrozen) { 1736 return; 1737 } 1738 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 1739 final boolean canScrollVertical = mLayout.canScrollVertically(); 1740 if (canScrollHorizontal || canScrollVertical) { 1741 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null); 1742 } 1743 } 1744 1745 /** 1746 * Scrolls the RV by 'dx' and 'dy' via calls to 1747 * {@link LayoutManager#scrollHorizontallyBy(int, Recycler, State)} and 1748 * {@link LayoutManager#scrollVerticallyBy(int, Recycler, State)}. 1749 * 1750 * Also sets how much of the scroll was actually consumed in 'consumed' parameter (indexes 0 and 1751 * 1 for the x axis and y axis, respectively). 1752 * 1753 * This method should only be called in the context of an existing scroll operation such that 1754 * any other necessary operations (such as a call to {@link #consumePendingUpdateOperations()}) 1755 * is already handled. 1756 */ 1757 private void scrollStep(int dx, int dy, @Nullable int[] consumed) { 1758 startInterceptRequestLayout(); 1759 onEnterLayoutOrScroll(); 1760 1761 TraceCompat.beginSection(TRACE_SCROLL_TAG); 1762 fillRemainingScrollValues(mState); 1763 1764 int consumedX = 0; 1765 int consumedY = 0; 1766 if (dx != 0) { 1767 consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); 1768 } 1769 if (dy != 0) { 1770 consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); 1771 } 1772 1773 TraceCompat.endSection(); 1774 repositionShadowingViews(); 1775 1776 onExitLayoutOrScroll(); 1777 stopInterceptRequestLayout(false); 1778 1779 if (consumed != null) { 1780 consumed[0] = consumedX; 1781 consumed[1] = consumedY; 1782 } 1783 } 1784 1785 /** 1786 * Helper method reflect data changes to the state. 1787 * <p> 1788 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change 1789 * but data actually changed. 1790 * <p> 1791 * This method consumes all deferred changes to avoid that case. 1792 */ 1793 void consumePendingUpdateOperations() { 1794 if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { 1795 TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 1796 dispatchLayout(); 1797 TraceCompat.endSection(); 1798 return; 1799 } 1800 if (!mAdapterHelper.hasPendingUpdates()) { 1801 return; 1802 } 1803 1804 // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any 1805 // of the visible items is affected and if not, just ignore the change. 1806 if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper 1807 .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE 1808 | AdapterHelper.UpdateOp.MOVE)) { 1809 TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); 1810 startInterceptRequestLayout(); 1811 onEnterLayoutOrScroll(); 1812 mAdapterHelper.preProcess(); 1813 if (!mLayoutWasDefered) { 1814 if (hasUpdatedView()) { 1815 dispatchLayout(); 1816 } else { 1817 // no need to layout, clean state 1818 mAdapterHelper.consumePostponedUpdates(); 1819 } 1820 } 1821 stopInterceptRequestLayout(true); 1822 onExitLayoutOrScroll(); 1823 TraceCompat.endSection(); 1824 } else if (mAdapterHelper.hasPendingUpdates()) { 1825 TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 1826 dispatchLayout(); 1827 TraceCompat.endSection(); 1828 } 1829 } 1830 1831 /** 1832 * @return True if an existing view holder needs to be updated 1833 */ 1834 private boolean hasUpdatedView() { 1835 final int childCount = mChildHelper.getChildCount(); 1836 for (int i = 0; i < childCount; i++) { 1837 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 1838 if (holder == null || holder.shouldIgnore()) { 1839 continue; 1840 } 1841 if (holder.isUpdated()) { 1842 return true; 1843 } 1844 } 1845 return false; 1846 } 1847 1848 /** 1849 * Does not perform bounds checking. Used by internal methods that have already validated input. 1850 * <p> 1851 * It also reports any unused scroll request to the related EdgeEffect. 1852 * 1853 * @param x The amount of horizontal scroll request 1854 * @param y The amount of vertical scroll request 1855 * @param ev The originating MotionEvent, or null if not from a touch event. 1856 * 1857 * @return Whether any scroll was consumed in either direction. 1858 */ 1859 boolean scrollByInternal(int x, int y, MotionEvent ev) { 1860 int unconsumedX = 0, unconsumedY = 0; 1861 int consumedX = 0, consumedY = 0; 1862 1863 consumePendingUpdateOperations(); 1864 if (mAdapter != null) { 1865 scrollStep(x, y, mScrollStepConsumed); 1866 consumedX = mScrollStepConsumed[0]; 1867 consumedY = mScrollStepConsumed[1]; 1868 unconsumedX = x - consumedX; 1869 unconsumedY = y - consumedY; 1870 } 1871 if (!mItemDecorations.isEmpty()) { 1872 invalidate(); 1873 } 1874 1875 if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, 1876 TYPE_TOUCH)) { 1877 // Update the last touch co-ords, taking any scroll offset into account 1878 mLastTouchX -= mScrollOffset[0]; 1879 mLastTouchY -= mScrollOffset[1]; 1880 if (ev != null) { 1881 ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 1882 } 1883 mNestedOffsets[0] += mScrollOffset[0]; 1884 mNestedOffsets[1] += mScrollOffset[1]; 1885 } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 1886 if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) { 1887 pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); 1888 } 1889 considerReleasingGlowsOnScroll(x, y); 1890 } 1891 if (consumedX != 0 || consumedY != 0) { 1892 dispatchOnScrolled(consumedX, consumedY); 1893 } 1894 if (!awakenScrollBars()) { 1895 invalidate(); 1896 } 1897 return consumedX != 0 || consumedY != 0; 1898 } 1899 1900 /** 1901 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal 1902 * range. This value is used to compute the length of the thumb within the scrollbar's track. 1903 * </p> 1904 * 1905 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1906 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> 1907 * 1908 * <p>Default implementation returns 0.</p> 1909 * 1910 * <p>If you want to support scroll bars, override 1911 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your 1912 * LayoutManager. </p> 1913 * 1914 * @return The horizontal offset of the scrollbar's thumb 1915 * @see RecyclerView.LayoutManager#computeHorizontalScrollOffset 1916 * (RecyclerView.State) 1917 */ 1918 @Override 1919 public int computeHorizontalScrollOffset() { 1920 if (mLayout == null) { 1921 return 0; 1922 } 1923 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; 1924 } 1925 1926 /** 1927 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the 1928 * horizontal range. This value is used to compute the length of the thumb within the 1929 * scrollbar's track.</p> 1930 * 1931 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1932 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> 1933 * 1934 * <p>Default implementation returns 0.</p> 1935 * 1936 * <p>If you want to support scroll bars, override 1937 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your 1938 * LayoutManager.</p> 1939 * 1940 * @return The horizontal extent of the scrollbar's thumb 1941 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) 1942 */ 1943 @Override 1944 public int computeHorizontalScrollExtent() { 1945 if (mLayout == null) { 1946 return 0; 1947 } 1948 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; 1949 } 1950 1951 /** 1952 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> 1953 * 1954 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1955 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> 1956 * 1957 * <p>Default implementation returns 0.</p> 1958 * 1959 * <p>If you want to support scroll bars, override 1960 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your 1961 * LayoutManager.</p> 1962 * 1963 * @return The total horizontal range represented by the vertical scrollbar 1964 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) 1965 */ 1966 @Override 1967 public int computeHorizontalScrollRange() { 1968 if (mLayout == null) { 1969 return 0; 1970 } 1971 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; 1972 } 1973 1974 /** 1975 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. 1976 * This value is used to compute the length of the thumb within the scrollbar's track. </p> 1977 * 1978 * <p>The range is expressed in arbitrary units that must be the same as the units used by 1979 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> 1980 * 1981 * <p>Default implementation returns 0.</p> 1982 * 1983 * <p>If you want to support scroll bars, override 1984 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your 1985 * LayoutManager.</p> 1986 * 1987 * @return The vertical offset of the scrollbar's thumb 1988 * @see RecyclerView.LayoutManager#computeVerticalScrollOffset 1989 * (RecyclerView.State) 1990 */ 1991 @Override 1992 public int computeVerticalScrollOffset() { 1993 if (mLayout == null) { 1994 return 0; 1995 } 1996 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; 1997 } 1998 1999 /** 2000 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. 2001 * This value is used to compute the length of the thumb within the scrollbar's track.</p> 2002 * 2003 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2004 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> 2005 * 2006 * <p>Default implementation returns 0.</p> 2007 * 2008 * <p>If you want to support scroll bars, override 2009 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your 2010 * LayoutManager.</p> 2011 * 2012 * @return The vertical extent of the scrollbar's thumb 2013 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) 2014 */ 2015 @Override 2016 public int computeVerticalScrollExtent() { 2017 if (mLayout == null) { 2018 return 0; 2019 } 2020 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; 2021 } 2022 2023 /** 2024 * <p>Compute the vertical range that the vertical scrollbar represents.</p> 2025 * 2026 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2027 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> 2028 * 2029 * <p>Default implementation returns 0.</p> 2030 * 2031 * <p>If you want to support scroll bars, override 2032 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your 2033 * LayoutManager.</p> 2034 * 2035 * @return The total vertical range represented by the vertical scrollbar 2036 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) 2037 */ 2038 @Override 2039 public int computeVerticalScrollRange() { 2040 if (mLayout == null) { 2041 return 0; 2042 } 2043 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; 2044 } 2045 2046 /** 2047 * This method should be called before any code that may trigger a child view to cause a call to 2048 * {@link RecyclerView#requestLayout()}. Doing so enables {@link RecyclerView} to avoid 2049 * reacting to additional redundant calls to {@link #requestLayout()}. 2050 * <p> 2051 * A call to this method must always be accompanied by a call to 2052 * {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a 2053 * child View to cause a call to {@link RecyclerView#requestLayout()}. 2054 * 2055 * @see #stopInterceptRequestLayout(boolean) 2056 */ 2057 void startInterceptRequestLayout() { 2058 mInterceptRequestLayoutDepth++; 2059 if (mInterceptRequestLayoutDepth == 1 && !mLayoutFrozen) { 2060 mLayoutWasDefered = false; 2061 } 2062 } 2063 2064 /** 2065 * This method should be called after any code that may trigger a child view to cause a call to 2066 * {@link RecyclerView#requestLayout()}. 2067 * <p> 2068 * A call to this method must always be accompanied by a call to 2069 * {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child 2070 * View to cause a call to {@link RecyclerView#requestLayout()}. 2071 * 2072 * @see #startInterceptRequestLayout() 2073 */ 2074 void stopInterceptRequestLayout(boolean performLayoutChildren) { 2075 if (mInterceptRequestLayoutDepth < 1) { 2076 //noinspection PointlessBooleanExpression 2077 if (DEBUG) { 2078 throw new IllegalStateException("stopInterceptRequestLayout was called more " 2079 + "times than startInterceptRequestLayout." 2080 + exceptionLabel()); 2081 } 2082 mInterceptRequestLayoutDepth = 1; 2083 } 2084 if (!performLayoutChildren && !mLayoutFrozen) { 2085 // Reset the layout request eaten counter. 2086 // This is necessary since eatRequest calls can be nested in which case the other 2087 // call will override the inner one. 2088 // for instance: 2089 // eat layout for process adapter updates 2090 // eat layout for dispatchLayout 2091 // a bunch of req layout calls arrive 2092 2093 mLayoutWasDefered = false; 2094 } 2095 if (mInterceptRequestLayoutDepth == 1) { 2096 // when layout is frozen we should delay dispatchLayout() 2097 if (performLayoutChildren && mLayoutWasDefered && !mLayoutFrozen 2098 && mLayout != null && mAdapter != null) { 2099 dispatchLayout(); 2100 } 2101 if (!mLayoutFrozen) { 2102 mLayoutWasDefered = false; 2103 } 2104 } 2105 mInterceptRequestLayoutDepth--; 2106 } 2107 2108 /** 2109 * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called, 2110 * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called; 2111 * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)}, 2112 * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and 2113 * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are 2114 * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be 2115 * called. 2116 * 2117 * <p> 2118 * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link 2119 * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( 2120 * RecyclerView, State, int)}. 2121 * <p> 2122 * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically 2123 * stop frozen. 2124 * <p> 2125 * Note: Running ItemAnimator is not stopped automatically, it's caller's 2126 * responsibility to call ItemAnimator.end(). 2127 * 2128 * @param frozen true to freeze layout and scroll, false to re-enable. 2129 */ 2130 public void setLayoutFrozen(boolean frozen) { 2131 if (frozen != mLayoutFrozen) { 2132 assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"); 2133 if (!frozen) { 2134 mLayoutFrozen = false; 2135 if (mLayoutWasDefered && mLayout != null && mAdapter != null) { 2136 requestLayout(); 2137 } 2138 mLayoutWasDefered = false; 2139 } else { 2140 final long now = SystemClock.uptimeMillis(); 2141 MotionEvent cancelEvent = MotionEvent.obtain(now, now, 2142 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 2143 onTouchEvent(cancelEvent); 2144 mLayoutFrozen = true; 2145 mIgnoreMotionEventTillDown = true; 2146 stopScroll(); 2147 } 2148 } 2149 } 2150 2151 /** 2152 * Returns true if layout and scroll are frozen. 2153 * 2154 * @return true if layout and scroll are frozen 2155 * @see #setLayoutFrozen(boolean) 2156 */ 2157 public boolean isLayoutFrozen() { 2158 return mLayoutFrozen; 2159 } 2160 2161 /** 2162 * Animate a scroll by the given amount of pixels along either axis. 2163 * 2164 * @param dx Pixels to scroll horizontally 2165 * @param dy Pixels to scroll vertically 2166 */ 2167 public void smoothScrollBy(@Px int dx, @Px int dy) { 2168 smoothScrollBy(dx, dy, null); 2169 } 2170 2171 /** 2172 * Animate a scroll by the given amount of pixels along either axis. 2173 * 2174 * @param dx Pixels to scroll horizontally 2175 * @param dy Pixels to scroll vertically 2176 * @param interpolator {@link Interpolator} to be used for scrolling. If it is 2177 * {@code null}, RecyclerView is going to use the default interpolator. 2178 */ 2179 public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) { 2180 if (mLayout == null) { 2181 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 2182 + "Call setLayoutManager with a non-null argument."); 2183 return; 2184 } 2185 if (mLayoutFrozen) { 2186 return; 2187 } 2188 if (!mLayout.canScrollHorizontally()) { 2189 dx = 0; 2190 } 2191 if (!mLayout.canScrollVertically()) { 2192 dy = 0; 2193 } 2194 if (dx != 0 || dy != 0) { 2195 mViewFlinger.smoothScrollBy(dx, dy, interpolator); 2196 } 2197 } 2198 2199 /** 2200 * Begin a standard fling with an initial velocity along each axis in pixels per second. 2201 * If the velocity given is below the system-defined minimum this method will return false 2202 * and no fling will occur. 2203 * 2204 * @param velocityX Initial horizontal velocity in pixels per second 2205 * @param velocityY Initial vertical velocity in pixels per second 2206 * @return true if the fling was started, false if the velocity was too low to fling or 2207 * LayoutManager does not support scrolling in the axis fling is issued. 2208 * 2209 * @see LayoutManager#canScrollVertically() 2210 * @see LayoutManager#canScrollHorizontally() 2211 */ 2212 public boolean fling(int velocityX, int velocityY) { 2213 if (mLayout == null) { 2214 Log.e(TAG, "Cannot fling without a LayoutManager set. " 2215 + "Call setLayoutManager with a non-null argument."); 2216 return false; 2217 } 2218 if (mLayoutFrozen) { 2219 return false; 2220 } 2221 2222 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2223 final boolean canScrollVertical = mLayout.canScrollVertically(); 2224 2225 if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { 2226 velocityX = 0; 2227 } 2228 if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { 2229 velocityY = 0; 2230 } 2231 if (velocityX == 0 && velocityY == 0) { 2232 // If we don't have any velocity, return false 2233 return false; 2234 } 2235 2236 if (!dispatchNestedPreFling(velocityX, velocityY)) { 2237 final boolean canScroll = canScrollHorizontal || canScrollVertical; 2238 dispatchNestedFling(velocityX, velocityY, canScroll); 2239 2240 if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { 2241 return true; 2242 } 2243 2244 if (canScroll) { 2245 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2246 if (canScrollHorizontal) { 2247 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2248 } 2249 if (canScrollVertical) { 2250 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2251 } 2252 startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH); 2253 2254 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 2255 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 2256 mViewFlinger.fling(velocityX, velocityY); 2257 return true; 2258 } 2259 } 2260 return false; 2261 } 2262 2263 /** 2264 * Stop any current scroll in progress, such as one started by 2265 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 2266 */ 2267 public void stopScroll() { 2268 setScrollState(SCROLL_STATE_IDLE); 2269 stopScrollersInternal(); 2270 } 2271 2272 /** 2273 * Similar to {@link #stopScroll()} but does not set the state. 2274 */ 2275 private void stopScrollersInternal() { 2276 mViewFlinger.stop(); 2277 if (mLayout != null) { 2278 mLayout.stopSmoothScroller(); 2279 } 2280 } 2281 2282 /** 2283 * Returns the minimum velocity to start a fling. 2284 * 2285 * @return The minimum velocity to start a fling 2286 */ 2287 public int getMinFlingVelocity() { 2288 return mMinFlingVelocity; 2289 } 2290 2291 2292 /** 2293 * Returns the maximum fling velocity used by this RecyclerView. 2294 * 2295 * @return The maximum fling velocity used by this RecyclerView. 2296 */ 2297 public int getMaxFlingVelocity() { 2298 return mMaxFlingVelocity; 2299 } 2300 2301 /** 2302 * Apply a pull to relevant overscroll glow effects 2303 */ 2304 private void pullGlows(float x, float overscrollX, float y, float overscrollY) { 2305 boolean invalidate = false; 2306 if (overscrollX < 0) { 2307 ensureLeftGlow(); 2308 EdgeEffectCompat.onPull(mLeftGlow, -overscrollX / getWidth(), 1f - y / getHeight()); 2309 invalidate = true; 2310 } else if (overscrollX > 0) { 2311 ensureRightGlow(); 2312 EdgeEffectCompat.onPull(mRightGlow, overscrollX / getWidth(), y / getHeight()); 2313 invalidate = true; 2314 } 2315 2316 if (overscrollY < 0) { 2317 ensureTopGlow(); 2318 EdgeEffectCompat.onPull(mTopGlow, -overscrollY / getHeight(), x / getWidth()); 2319 invalidate = true; 2320 } else if (overscrollY > 0) { 2321 ensureBottomGlow(); 2322 EdgeEffectCompat.onPull(mBottomGlow, overscrollY / getHeight(), 1f - x / getWidth()); 2323 invalidate = true; 2324 } 2325 2326 if (invalidate || overscrollX != 0 || overscrollY != 0) { 2327 ViewCompat.postInvalidateOnAnimation(this); 2328 } 2329 } 2330 2331 private void releaseGlows() { 2332 boolean needsInvalidate = false; 2333 if (mLeftGlow != null) { 2334 mLeftGlow.onRelease(); 2335 needsInvalidate = mLeftGlow.isFinished(); 2336 } 2337 if (mTopGlow != null) { 2338 mTopGlow.onRelease(); 2339 needsInvalidate |= mTopGlow.isFinished(); 2340 } 2341 if (mRightGlow != null) { 2342 mRightGlow.onRelease(); 2343 needsInvalidate |= mRightGlow.isFinished(); 2344 } 2345 if (mBottomGlow != null) { 2346 mBottomGlow.onRelease(); 2347 needsInvalidate |= mBottomGlow.isFinished(); 2348 } 2349 if (needsInvalidate) { 2350 ViewCompat.postInvalidateOnAnimation(this); 2351 } 2352 } 2353 2354 void considerReleasingGlowsOnScroll(int dx, int dy) { 2355 boolean needsInvalidate = false; 2356 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { 2357 mLeftGlow.onRelease(); 2358 needsInvalidate = mLeftGlow.isFinished(); 2359 } 2360 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { 2361 mRightGlow.onRelease(); 2362 needsInvalidate |= mRightGlow.isFinished(); 2363 } 2364 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { 2365 mTopGlow.onRelease(); 2366 needsInvalidate |= mTopGlow.isFinished(); 2367 } 2368 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { 2369 mBottomGlow.onRelease(); 2370 needsInvalidate |= mBottomGlow.isFinished(); 2371 } 2372 if (needsInvalidate) { 2373 ViewCompat.postInvalidateOnAnimation(this); 2374 } 2375 } 2376 2377 void absorbGlows(int velocityX, int velocityY) { 2378 if (velocityX < 0) { 2379 ensureLeftGlow(); 2380 mLeftGlow.onAbsorb(-velocityX); 2381 } else if (velocityX > 0) { 2382 ensureRightGlow(); 2383 mRightGlow.onAbsorb(velocityX); 2384 } 2385 2386 if (velocityY < 0) { 2387 ensureTopGlow(); 2388 mTopGlow.onAbsorb(-velocityY); 2389 } else if (velocityY > 0) { 2390 ensureBottomGlow(); 2391 mBottomGlow.onAbsorb(velocityY); 2392 } 2393 2394 if (velocityX != 0 || velocityY != 0) { 2395 ViewCompat.postInvalidateOnAnimation(this); 2396 } 2397 } 2398 2399 void ensureLeftGlow() { 2400 if (mLeftGlow != null) { 2401 return; 2402 } 2403 mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT); 2404 if (mClipToPadding) { 2405 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 2406 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 2407 } else { 2408 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 2409 } 2410 } 2411 2412 void ensureRightGlow() { 2413 if (mRightGlow != null) { 2414 return; 2415 } 2416 mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT); 2417 if (mClipToPadding) { 2418 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 2419 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 2420 } else { 2421 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 2422 } 2423 } 2424 2425 void ensureTopGlow() { 2426 if (mTopGlow != null) { 2427 return; 2428 } 2429 mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP); 2430 if (mClipToPadding) { 2431 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 2432 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 2433 } else { 2434 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 2435 } 2436 2437 } 2438 2439 void ensureBottomGlow() { 2440 if (mBottomGlow != null) { 2441 return; 2442 } 2443 mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM); 2444 if (mClipToPadding) { 2445 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 2446 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 2447 } else { 2448 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 2449 } 2450 } 2451 2452 void invalidateGlows() { 2453 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; 2454 } 2455 2456 /** 2457 * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}. 2458 * <p> 2459 * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared 2460 * and new effects are created as needed using 2461 * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)} 2462 * 2463 * @param edgeEffectFactory The {@link EdgeEffectFactory} instance. 2464 */ 2465 public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) { 2466 Preconditions.checkNotNull(edgeEffectFactory); 2467 mEdgeEffectFactory = edgeEffectFactory; 2468 invalidateGlows(); 2469 } 2470 2471 /** 2472 * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing 2473 * was set. 2474 * 2475 * @return The previously set {@link EdgeEffectFactory} 2476 * @see #setEdgeEffectFactory(EdgeEffectFactory) 2477 */ 2478 @NonNull 2479 public EdgeEffectFactory getEdgeEffectFactory() { 2480 return mEdgeEffectFactory; 2481 } 2482 2483 /** 2484 * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are 2485 * in the Adapter but not visible in the UI), it employs a more involved focus search strategy 2486 * that differs from other ViewGroups. 2487 * <p> 2488 * It first does a focus search within the RecyclerView. If this search finds a View that is in 2489 * the focus direction with respect to the currently focused View, RecyclerView returns that 2490 * child as the next focus target. When it cannot find such child, it calls 2491 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views 2492 * in the focus search direction. If LayoutManager adds a View that matches the 2493 * focus search criteria, it will be returned as the focus search result. Otherwise, 2494 * RecyclerView will call parent to handle the focus search like a regular ViewGroup. 2495 * <p> 2496 * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that 2497 * is not in the focus direction is still valid focus target which may not be the desired 2498 * behavior if the Adapter has more children in the focus direction. To handle this case, 2499 * RecyclerView converts the focus direction to an absolute direction and makes a preliminary 2500 * focus search in that direction. If there are no Views to gain focus, it will call 2501 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a 2502 * focus search with the original (relative) direction. This allows RecyclerView to provide 2503 * better candidates to the focus search while still allowing the view system to take focus from 2504 * the RecyclerView and give it to a more suitable child if such child exists. 2505 * 2506 * @param focused The view that currently has focus 2507 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 2508 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}, 2509 * {@link View#FOCUS_BACKWARD} or 0 for not applicable. 2510 * 2511 * @return A new View that can be the next focus after the focused View 2512 */ 2513 @Override 2514 public View focusSearch(View focused, int direction) { 2515 View result = mLayout.onInterceptFocusSearch(focused, direction); 2516 if (result != null) { 2517 return result; 2518 } 2519 final boolean canRunFocusFailure = mAdapter != null && mLayout != null 2520 && !isComputingLayout() && !mLayoutFrozen; 2521 2522 final FocusFinder ff = FocusFinder.getInstance(); 2523 if (canRunFocusFailure 2524 && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { 2525 // convert direction to absolute direction and see if we have a view there and if not 2526 // tell LayoutManager to add if it can. 2527 boolean needsFocusFailureLayout = false; 2528 if (mLayout.canScrollVertically()) { 2529 final int absDir = 2530 direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; 2531 final View found = ff.findNextFocus(this, focused, absDir); 2532 needsFocusFailureLayout = found == null; 2533 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { 2534 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. 2535 direction = absDir; 2536 } 2537 } 2538 if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { 2539 boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; 2540 final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl 2541 ? View.FOCUS_RIGHT : View.FOCUS_LEFT; 2542 final View found = ff.findNextFocus(this, focused, absDir); 2543 needsFocusFailureLayout = found == null; 2544 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { 2545 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. 2546 direction = absDir; 2547 } 2548 } 2549 if (needsFocusFailureLayout) { 2550 consumePendingUpdateOperations(); 2551 final View focusedItemView = findContainingItemView(focused); 2552 if (focusedItemView == null) { 2553 // panic, focused view is not a child anymore, cannot call super. 2554 return null; 2555 } 2556 startInterceptRequestLayout(); 2557 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 2558 stopInterceptRequestLayout(false); 2559 } 2560 result = ff.findNextFocus(this, focused, direction); 2561 } else { 2562 result = ff.findNextFocus(this, focused, direction); 2563 if (result == null && canRunFocusFailure) { 2564 consumePendingUpdateOperations(); 2565 final View focusedItemView = findContainingItemView(focused); 2566 if (focusedItemView == null) { 2567 // panic, focused view is not a child anymore, cannot call super. 2568 return null; 2569 } 2570 startInterceptRequestLayout(); 2571 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 2572 stopInterceptRequestLayout(false); 2573 } 2574 } 2575 if (result != null && !result.hasFocusable()) { 2576 if (getFocusedChild() == null) { 2577 // Scrolling to this unfocusable view is not meaningful since there is no currently 2578 // focused view which RV needs to keep visible. 2579 return super.focusSearch(focused, direction); 2580 } 2581 // If the next view returned by onFocusSearchFailed in layout manager has no focusable 2582 // views, we still scroll to that view in order to make it visible on the screen. 2583 // If it's focusable, framework already calls RV's requestChildFocus which handles 2584 // bringing this newly focused item onto the screen. 2585 requestChildOnScreen(result, null); 2586 return focused; 2587 } 2588 return isPreferredNextFocus(focused, result, direction) 2589 ? result : super.focusSearch(focused, direction); 2590 } 2591 2592 /** 2593 * Checks if the new focus candidate is a good enough candidate such that RecyclerView will 2594 * assign it as the next focus View instead of letting view hierarchy decide. 2595 * A good candidate means a View that is aligned in the focus direction wrt the focused View 2596 * and is not the RecyclerView itself. 2597 * When this method returns false, RecyclerView will let the parent make the decision so the 2598 * same View may still get the focus as a result of that search. 2599 */ 2600 private boolean isPreferredNextFocus(View focused, View next, int direction) { 2601 if (next == null || next == this) { 2602 return false; 2603 } 2604 // panic, result view is not a child anymore, maybe workaround b/37864393 2605 if (findContainingItemView(next) == null) { 2606 return false; 2607 } 2608 if (focused == null) { 2609 return true; 2610 } 2611 // panic, focused view is not a child anymore, maybe workaround b/37864393 2612 if (findContainingItemView(focused) == null) { 2613 return true; 2614 } 2615 2616 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 2617 mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); 2618 offsetDescendantRectToMyCoords(focused, mTempRect); 2619 offsetDescendantRectToMyCoords(next, mTempRect2); 2620 final int rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL ? -1 : 1; 2621 int rightness = 0; 2622 if ((mTempRect.left < mTempRect2.left 2623 || mTempRect.right <= mTempRect2.left) 2624 && mTempRect.right < mTempRect2.right) { 2625 rightness = 1; 2626 } else if ((mTempRect.right > mTempRect2.right 2627 || mTempRect.left >= mTempRect2.right) 2628 && mTempRect.left > mTempRect2.left) { 2629 rightness = -1; 2630 } 2631 int downness = 0; 2632 if ((mTempRect.top < mTempRect2.top 2633 || mTempRect.bottom <= mTempRect2.top) 2634 && mTempRect.bottom < mTempRect2.bottom) { 2635 downness = 1; 2636 } else if ((mTempRect.bottom > mTempRect2.bottom 2637 || mTempRect.top >= mTempRect2.bottom) 2638 && mTempRect.top > mTempRect2.top) { 2639 downness = -1; 2640 } 2641 switch (direction) { 2642 case View.FOCUS_LEFT: 2643 return rightness < 0; 2644 case View.FOCUS_RIGHT: 2645 return rightness > 0; 2646 case View.FOCUS_UP: 2647 return downness < 0; 2648 case View.FOCUS_DOWN: 2649 return downness > 0; 2650 case View.FOCUS_FORWARD: 2651 return downness > 0 || (downness == 0 && rightness * rtl >= 0); 2652 case View.FOCUS_BACKWARD: 2653 return downness < 0 || (downness == 0 && rightness * rtl <= 0); 2654 } 2655 throw new IllegalArgumentException("Invalid direction: " + direction + exceptionLabel()); 2656 } 2657 2658 @Override 2659 public void requestChildFocus(View child, View focused) { 2660 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { 2661 requestChildOnScreen(child, focused); 2662 } 2663 super.requestChildFocus(child, focused); 2664 } 2665 2666 /** 2667 * Requests that the given child of the RecyclerView be positioned onto the screen. This method 2668 * can be called for both unfocusable and focusable child views. For unfocusable child views, 2669 * the {@param focused} parameter passed is null, whereas for a focusable child, this parameter 2670 * indicates the actual descendant view within this child view that holds the focus. 2671 * @param child The child view of this RecyclerView that wants to come onto the screen. 2672 * @param focused The descendant view that actually has the focus if child is focusable, null 2673 * otherwise. 2674 */ 2675 private void requestChildOnScreen(@NonNull View child, @Nullable View focused) { 2676 View rectView = (focused != null) ? focused : child; 2677 mTempRect.set(0, 0, rectView.getWidth(), rectView.getHeight()); 2678 2679 // get item decor offsets w/o refreshing. If they are invalid, there will be another 2680 // layout pass to fix them, then it is LayoutManager's responsibility to keep focused 2681 // View in viewport. 2682 final ViewGroup.LayoutParams focusedLayoutParams = rectView.getLayoutParams(); 2683 if (focusedLayoutParams instanceof LayoutParams) { 2684 // if focused child has item decors, use them. Otherwise, ignore. 2685 final LayoutParams lp = (LayoutParams) focusedLayoutParams; 2686 if (!lp.mInsetsDirty) { 2687 final Rect insets = lp.mDecorInsets; 2688 mTempRect.left -= insets.left; 2689 mTempRect.right += insets.right; 2690 mTempRect.top -= insets.top; 2691 mTempRect.bottom += insets.bottom; 2692 } 2693 } 2694 2695 if (focused != null) { 2696 offsetDescendantRectToMyCoords(focused, mTempRect); 2697 offsetRectIntoDescendantCoords(child, mTempRect); 2698 } 2699 mLayout.requestChildRectangleOnScreen(this, child, mTempRect, !mFirstLayoutComplete, 2700 (focused == null)); 2701 } 2702 2703 @Override 2704 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 2705 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); 2706 } 2707 2708 @Override 2709 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 2710 if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { 2711 super.addFocusables(views, direction, focusableMode); 2712 } 2713 } 2714 2715 @Override 2716 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 2717 if (isComputingLayout()) { 2718 // if we are in the middle of a layout calculation, don't let any child take focus. 2719 // RV will handle it after layout calculation is finished. 2720 return false; 2721 } 2722 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 2723 } 2724 2725 @Override 2726 protected void onAttachedToWindow() { 2727 super.onAttachedToWindow(); 2728 mLayoutOrScrollCounter = 0; 2729 mIsAttached = true; 2730 mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); 2731 if (mLayout != null) { 2732 mLayout.dispatchAttachedToWindow(this); 2733 } 2734 mPostedAnimatorRunner = false; 2735 2736 if (ALLOW_THREAD_GAP_WORK) { 2737 // Register with gap worker 2738 mGapWorker = GapWorker.sGapWorker.get(); 2739 if (mGapWorker == null) { 2740 mGapWorker = new GapWorker(); 2741 2742 // break 60 fps assumption if data from display appears valid 2743 // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) 2744 Display display = ViewCompat.getDisplay(this); 2745 float refreshRate = 60.0f; 2746 if (!isInEditMode() && display != null) { 2747 float displayRefreshRate = display.getRefreshRate(); 2748 if (displayRefreshRate >= 30.0f) { 2749 refreshRate = displayRefreshRate; 2750 } 2751 } 2752 mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); 2753 GapWorker.sGapWorker.set(mGapWorker); 2754 } 2755 mGapWorker.add(this); 2756 } 2757 } 2758 2759 @Override 2760 protected void onDetachedFromWindow() { 2761 super.onDetachedFromWindow(); 2762 if (mItemAnimator != null) { 2763 mItemAnimator.endAnimations(); 2764 } 2765 stopScroll(); 2766 mIsAttached = false; 2767 if (mLayout != null) { 2768 mLayout.dispatchDetachedFromWindow(this, mRecycler); 2769 } 2770 mPendingAccessibilityImportanceChange.clear(); 2771 removeCallbacks(mItemAnimatorRunner); 2772 mViewInfoStore.onDetach(); 2773 2774 if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) { 2775 // Unregister with gap worker 2776 mGapWorker.remove(this); 2777 mGapWorker = null; 2778 } 2779 } 2780 2781 /** 2782 * Returns true if RecyclerView is attached to window. 2783 */ 2784 @Override 2785 public boolean isAttachedToWindow() { 2786 return mIsAttached; 2787 } 2788 2789 /** 2790 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 2791 * {@link IllegalStateException} if it <b>is not</b>. 2792 * 2793 * @param message The message for the exception. Can be null. 2794 * @see #assertNotInLayoutOrScroll(String) 2795 */ 2796 void assertInLayoutOrScroll(String message) { 2797 if (!isComputingLayout()) { 2798 if (message == null) { 2799 throw new IllegalStateException("Cannot call this method unless RecyclerView is " 2800 + "computing a layout or scrolling" + exceptionLabel()); 2801 } 2802 throw new IllegalStateException(message + exceptionLabel()); 2803 2804 } 2805 } 2806 2807 /** 2808 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 2809 * {@link IllegalStateException} if it <b>is</b>. 2810 * 2811 * @param message The message for the exception. Can be null. 2812 * @see #assertInLayoutOrScroll(String) 2813 */ 2814 void assertNotInLayoutOrScroll(String message) { 2815 if (isComputingLayout()) { 2816 if (message == null) { 2817 throw new IllegalStateException("Cannot call this method while RecyclerView is " 2818 + "computing a layout or scrolling" + exceptionLabel()); 2819 } 2820 throw new IllegalStateException(message); 2821 } 2822 if (mDispatchScrollCounter > 0) { 2823 Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might" 2824 + "be run during a measure & layout pass where you cannot change the" 2825 + "RecyclerView data. Any method call that might change the structure" 2826 + "of the RecyclerView or the adapter contents should be postponed to" 2827 + "the next frame.", 2828 new IllegalStateException("" + exceptionLabel())); 2829 } 2830 } 2831 2832 /** 2833 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 2834 * to child views or this view's standard scrolling behavior. 2835 * 2836 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 2837 * returns true from 2838 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 2839 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 2840 * for each incoming MotionEvent until the end of the gesture.</p> 2841 * 2842 * @param listener Listener to add 2843 * @see SimpleOnItemTouchListener 2844 */ 2845 public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) { 2846 mOnItemTouchListeners.add(listener); 2847 } 2848 2849 /** 2850 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 2851 * 2852 * @param listener Listener to remove 2853 */ 2854 public void removeOnItemTouchListener(@NonNull OnItemTouchListener listener) { 2855 mOnItemTouchListeners.remove(listener); 2856 if (mActiveOnItemTouchListener == listener) { 2857 mActiveOnItemTouchListener = null; 2858 } 2859 } 2860 2861 private boolean dispatchOnItemTouchIntercept(MotionEvent e) { 2862 final int action = e.getAction(); 2863 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { 2864 mActiveOnItemTouchListener = null; 2865 } 2866 2867 final int listenerCount = mOnItemTouchListeners.size(); 2868 for (int i = 0; i < listenerCount; i++) { 2869 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 2870 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 2871 mActiveOnItemTouchListener = listener; 2872 return true; 2873 } 2874 } 2875 return false; 2876 } 2877 2878 private boolean dispatchOnItemTouch(MotionEvent e) { 2879 final int action = e.getAction(); 2880 if (mActiveOnItemTouchListener != null) { 2881 if (action == MotionEvent.ACTION_DOWN) { 2882 // Stale state from a previous gesture, we're starting a new one. Clear it. 2883 mActiveOnItemTouchListener = null; 2884 } else { 2885 mActiveOnItemTouchListener.onTouchEvent(this, e); 2886 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 2887 // Clean up for the next gesture. 2888 mActiveOnItemTouchListener = null; 2889 } 2890 return true; 2891 } 2892 } 2893 2894 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept 2895 // as called from onInterceptTouchEvent; skip it. 2896 if (action != MotionEvent.ACTION_DOWN) { 2897 final int listenerCount = mOnItemTouchListeners.size(); 2898 for (int i = 0; i < listenerCount; i++) { 2899 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 2900 if (listener.onInterceptTouchEvent(this, e)) { 2901 mActiveOnItemTouchListener = listener; 2902 return true; 2903 } 2904 } 2905 } 2906 return false; 2907 } 2908 2909 @Override 2910 public boolean onInterceptTouchEvent(MotionEvent e) { 2911 if (mLayoutFrozen) { 2912 // When layout is frozen, RV does not intercept the motion event. 2913 // A child view e.g. a button may still get the click. 2914 return false; 2915 } 2916 if (dispatchOnItemTouchIntercept(e)) { 2917 cancelTouch(); 2918 return true; 2919 } 2920 2921 if (mLayout == null) { 2922 return false; 2923 } 2924 2925 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 2926 final boolean canScrollVertically = mLayout.canScrollVertically(); 2927 2928 if (mVelocityTracker == null) { 2929 mVelocityTracker = VelocityTracker.obtain(); 2930 } 2931 mVelocityTracker.addMovement(e); 2932 2933 final int action = e.getActionMasked(); 2934 final int actionIndex = e.getActionIndex(); 2935 2936 switch (action) { 2937 case MotionEvent.ACTION_DOWN: 2938 if (mIgnoreMotionEventTillDown) { 2939 mIgnoreMotionEventTillDown = false; 2940 } 2941 mScrollPointerId = e.getPointerId(0); 2942 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 2943 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 2944 2945 if (mScrollState == SCROLL_STATE_SETTLING) { 2946 getParent().requestDisallowInterceptTouchEvent(true); 2947 setScrollState(SCROLL_STATE_DRAGGING); 2948 } 2949 2950 // Clear the nested offsets 2951 mNestedOffsets[0] = mNestedOffsets[1] = 0; 2952 2953 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2954 if (canScrollHorizontally) { 2955 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2956 } 2957 if (canScrollVertically) { 2958 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2959 } 2960 startNestedScroll(nestedScrollAxis, TYPE_TOUCH); 2961 break; 2962 2963 case MotionEvent.ACTION_POINTER_DOWN: 2964 mScrollPointerId = e.getPointerId(actionIndex); 2965 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 2966 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 2967 break; 2968 2969 case MotionEvent.ACTION_MOVE: { 2970 final int index = e.findPointerIndex(mScrollPointerId); 2971 if (index < 0) { 2972 Log.e(TAG, "Error processing scroll; pointer index for id " 2973 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 2974 return false; 2975 } 2976 2977 final int x = (int) (e.getX(index) + 0.5f); 2978 final int y = (int) (e.getY(index) + 0.5f); 2979 if (mScrollState != SCROLL_STATE_DRAGGING) { 2980 final int dx = x - mInitialTouchX; 2981 final int dy = y - mInitialTouchY; 2982 boolean startScroll = false; 2983 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 2984 mLastTouchX = x; 2985 startScroll = true; 2986 } 2987 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 2988 mLastTouchY = y; 2989 startScroll = true; 2990 } 2991 if (startScroll) { 2992 setScrollState(SCROLL_STATE_DRAGGING); 2993 } 2994 } 2995 } break; 2996 2997 case MotionEvent.ACTION_POINTER_UP: { 2998 onPointerUp(e); 2999 } break; 3000 3001 case MotionEvent.ACTION_UP: { 3002 mVelocityTracker.clear(); 3003 stopNestedScroll(TYPE_TOUCH); 3004 } break; 3005 3006 case MotionEvent.ACTION_CANCEL: { 3007 cancelTouch(); 3008 } 3009 } 3010 return mScrollState == SCROLL_STATE_DRAGGING; 3011 } 3012 3013 @Override 3014 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 3015 final int listenerCount = mOnItemTouchListeners.size(); 3016 for (int i = 0; i < listenerCount; i++) { 3017 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 3018 listener.onRequestDisallowInterceptTouchEvent(disallowIntercept); 3019 } 3020 super.requestDisallowInterceptTouchEvent(disallowIntercept); 3021 } 3022 3023 @Override 3024 public boolean onTouchEvent(MotionEvent e) { 3025 if (mLayoutFrozen || mIgnoreMotionEventTillDown) { 3026 return false; 3027 } 3028 if (dispatchOnItemTouch(e)) { 3029 cancelTouch(); 3030 return true; 3031 } 3032 3033 if (mLayout == null) { 3034 return false; 3035 } 3036 3037 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 3038 final boolean canScrollVertically = mLayout.canScrollVertically(); 3039 3040 if (mVelocityTracker == null) { 3041 mVelocityTracker = VelocityTracker.obtain(); 3042 } 3043 boolean eventAddedToVelocityTracker = false; 3044 3045 final MotionEvent vtev = MotionEvent.obtain(e); 3046 final int action = e.getActionMasked(); 3047 final int actionIndex = e.getActionIndex(); 3048 3049 if (action == MotionEvent.ACTION_DOWN) { 3050 mNestedOffsets[0] = mNestedOffsets[1] = 0; 3051 } 3052 vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); 3053 3054 switch (action) { 3055 case MotionEvent.ACTION_DOWN: { 3056 mScrollPointerId = e.getPointerId(0); 3057 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 3058 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 3059 3060 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 3061 if (canScrollHorizontally) { 3062 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 3063 } 3064 if (canScrollVertically) { 3065 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 3066 } 3067 startNestedScroll(nestedScrollAxis, TYPE_TOUCH); 3068 } break; 3069 3070 case MotionEvent.ACTION_POINTER_DOWN: { 3071 mScrollPointerId = e.getPointerId(actionIndex); 3072 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 3073 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 3074 } break; 3075 3076 case MotionEvent.ACTION_MOVE: { 3077 final int index = e.findPointerIndex(mScrollPointerId); 3078 if (index < 0) { 3079 Log.e(TAG, "Error processing scroll; pointer index for id " 3080 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 3081 return false; 3082 } 3083 3084 final int x = (int) (e.getX(index) + 0.5f); 3085 final int y = (int) (e.getY(index) + 0.5f); 3086 int dx = mLastTouchX - x; 3087 int dy = mLastTouchY - y; 3088 3089 if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) { 3090 dx -= mScrollConsumed[0]; 3091 dy -= mScrollConsumed[1]; 3092 vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 3093 // Updated the nested offsets 3094 mNestedOffsets[0] += mScrollOffset[0]; 3095 mNestedOffsets[1] += mScrollOffset[1]; 3096 } 3097 3098 if (mScrollState != SCROLL_STATE_DRAGGING) { 3099 boolean startScroll = false; 3100 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 3101 if (dx > 0) { 3102 dx -= mTouchSlop; 3103 } else { 3104 dx += mTouchSlop; 3105 } 3106 startScroll = true; 3107 } 3108 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 3109 if (dy > 0) { 3110 dy -= mTouchSlop; 3111 } else { 3112 dy += mTouchSlop; 3113 } 3114 startScroll = true; 3115 } 3116 if (startScroll) { 3117 setScrollState(SCROLL_STATE_DRAGGING); 3118 } 3119 } 3120 3121 if (mScrollState == SCROLL_STATE_DRAGGING) { 3122 mLastTouchX = x - mScrollOffset[0]; 3123 mLastTouchY = y - mScrollOffset[1]; 3124 3125 if (scrollByInternal( 3126 canScrollHorizontally ? dx : 0, 3127 canScrollVertically ? dy : 0, 3128 vtev)) { 3129 getParent().requestDisallowInterceptTouchEvent(true); 3130 } 3131 if (mGapWorker != null && (dx != 0 || dy != 0)) { 3132 mGapWorker.postFromTraversal(this, dx, dy); 3133 } 3134 } 3135 } break; 3136 3137 case MotionEvent.ACTION_POINTER_UP: { 3138 onPointerUp(e); 3139 } break; 3140 3141 case MotionEvent.ACTION_UP: { 3142 mVelocityTracker.addMovement(vtev); 3143 eventAddedToVelocityTracker = true; 3144 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 3145 final float xvel = canScrollHorizontally 3146 ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; 3147 final float yvel = canScrollVertically 3148 ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; 3149 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 3150 setScrollState(SCROLL_STATE_IDLE); 3151 } 3152 resetTouch(); 3153 } break; 3154 3155 case MotionEvent.ACTION_CANCEL: { 3156 cancelTouch(); 3157 } break; 3158 } 3159 3160 if (!eventAddedToVelocityTracker) { 3161 mVelocityTracker.addMovement(vtev); 3162 } 3163 vtev.recycle(); 3164 3165 return true; 3166 } 3167 3168 private void resetTouch() { 3169 if (mVelocityTracker != null) { 3170 mVelocityTracker.clear(); 3171 } 3172 stopNestedScroll(TYPE_TOUCH); 3173 releaseGlows(); 3174 } 3175 3176 private void cancelTouch() { 3177 resetTouch(); 3178 setScrollState(SCROLL_STATE_IDLE); 3179 } 3180 3181 private void onPointerUp(MotionEvent e) { 3182 final int actionIndex = e.getActionIndex(); 3183 if (e.getPointerId(actionIndex) == mScrollPointerId) { 3184 // Pick a new pointer to pick up the slack. 3185 final int newIndex = actionIndex == 0 ? 1 : 0; 3186 mScrollPointerId = e.getPointerId(newIndex); 3187 mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); 3188 mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); 3189 } 3190 } 3191 3192 @Override 3193 public boolean onGenericMotionEvent(MotionEvent event) { 3194 if (mLayout == null) { 3195 return false; 3196 } 3197 if (mLayoutFrozen) { 3198 return false; 3199 } 3200 if (event.getAction() == MotionEventCompat.ACTION_SCROLL) { 3201 final float vScroll, hScroll; 3202 if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { 3203 if (mLayout.canScrollVertically()) { 3204 // Inverse the sign of the vertical scroll to align the scroll orientation 3205 // with AbsListView. 3206 vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 3207 } else { 3208 vScroll = 0f; 3209 } 3210 if (mLayout.canScrollHorizontally()) { 3211 hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 3212 } else { 3213 hScroll = 0f; 3214 } 3215 } else if ((event.getSource() & InputDeviceCompat.SOURCE_ROTARY_ENCODER) != 0) { 3216 final float axisScroll = event.getAxisValue(MotionEventCompat.AXIS_SCROLL); 3217 if (mLayout.canScrollVertically()) { 3218 // Invert the sign of the vertical scroll to align the scroll orientation 3219 // with AbsListView. 3220 vScroll = -axisScroll; 3221 hScroll = 0f; 3222 } else if (mLayout.canScrollHorizontally()) { 3223 vScroll = 0f; 3224 hScroll = axisScroll; 3225 } else { 3226 vScroll = 0f; 3227 hScroll = 0f; 3228 } 3229 } else { 3230 vScroll = 0f; 3231 hScroll = 0f; 3232 } 3233 3234 if (vScroll != 0 || hScroll != 0) { 3235 scrollByInternal((int) (hScroll * mScaledHorizontalScrollFactor), 3236 (int) (vScroll * mScaledVerticalScrollFactor), event); 3237 } 3238 } 3239 return false; 3240 } 3241 3242 @Override 3243 protected void onMeasure(int widthSpec, int heightSpec) { 3244 if (mLayout == null) { 3245 defaultOnMeasure(widthSpec, heightSpec); 3246 return; 3247 } 3248 if (mLayout.isAutoMeasureEnabled()) { 3249 final int widthMode = MeasureSpec.getMode(widthSpec); 3250 final int heightMode = MeasureSpec.getMode(heightSpec); 3251 3252 /** 3253 * This specific call should be considered deprecated and replaced with 3254 * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could 3255 * break existing third party code but all documentation directs developers to not 3256 * override {@link LayoutManager#onMeasure(int, int)} when 3257 * {@link LayoutManager#isAutoMeasureEnabled()} returns true. 3258 */ 3259 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3260 3261 final boolean measureSpecModeIsExactly = 3262 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; 3263 if (measureSpecModeIsExactly || mAdapter == null) { 3264 return; 3265 } 3266 3267 if (mState.mLayoutStep == State.STEP_START) { 3268 dispatchLayoutStep1(); 3269 } 3270 // set dimensions in 2nd step. Pre-layout should happen with old dimensions for 3271 // consistency 3272 mLayout.setMeasureSpecs(widthSpec, heightSpec); 3273 mState.mIsMeasuring = true; 3274 dispatchLayoutStep2(); 3275 3276 // now we can get the width and height from the children. 3277 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 3278 3279 // if RecyclerView has non-exact width and height and if there is at least one child 3280 // which also has non-exact width & height, we have to re-measure. 3281 if (mLayout.shouldMeasureTwice()) { 3282 mLayout.setMeasureSpecs( 3283 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 3284 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 3285 mState.mIsMeasuring = true; 3286 dispatchLayoutStep2(); 3287 // now we can get the width and height from the children. 3288 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 3289 } 3290 } else { 3291 if (mHasFixedSize) { 3292 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3293 return; 3294 } 3295 // custom onMeasure 3296 if (mAdapterUpdateDuringMeasure) { 3297 startInterceptRequestLayout(); 3298 onEnterLayoutOrScroll(); 3299 processAdapterUpdatesAndSetAnimationFlags(); 3300 onExitLayoutOrScroll(); 3301 3302 if (mState.mRunPredictiveAnimations) { 3303 mState.mInPreLayout = true; 3304 } else { 3305 // consume remaining updates to provide a consistent state with the layout pass. 3306 mAdapterHelper.consumeUpdatesInOnePass(); 3307 mState.mInPreLayout = false; 3308 } 3309 mAdapterUpdateDuringMeasure = false; 3310 stopInterceptRequestLayout(false); 3311 } else if (mState.mRunPredictiveAnimations) { 3312 // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: 3313 // this means there is already an onMeasure() call performed to handle the pending 3314 // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout 3315 // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time 3316 // because getViewForPosition() will crash when LM uses a child to measure. 3317 setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); 3318 return; 3319 } 3320 3321 if (mAdapter != null) { 3322 mState.mItemCount = mAdapter.getItemCount(); 3323 } else { 3324 mState.mItemCount = 0; 3325 } 3326 startInterceptRequestLayout(); 3327 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 3328 stopInterceptRequestLayout(false); 3329 mState.mInPreLayout = false; // clear 3330 } 3331 } 3332 3333 /** 3334 * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios 3335 * where this RecyclerView is otherwise lacking better information. 3336 */ 3337 void defaultOnMeasure(int widthSpec, int heightSpec) { 3338 // calling LayoutManager here is not pretty but that API is already public and it is better 3339 // than creating another method since this is internal. 3340 final int width = LayoutManager.chooseSize(widthSpec, 3341 getPaddingLeft() + getPaddingRight(), 3342 ViewCompat.getMinimumWidth(this)); 3343 final int height = LayoutManager.chooseSize(heightSpec, 3344 getPaddingTop() + getPaddingBottom(), 3345 ViewCompat.getMinimumHeight(this)); 3346 3347 setMeasuredDimension(width, height); 3348 } 3349 3350 @Override 3351 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 3352 super.onSizeChanged(w, h, oldw, oldh); 3353 if (w != oldw || h != oldh) { 3354 invalidateGlows(); 3355 // layout's w/h are updated during measure/layout steps. 3356 } 3357 } 3358 3359 /** 3360 * Sets the {@link ItemAnimator} that will handle animations involving changes 3361 * to the items in this RecyclerView. By default, RecyclerView instantiates and 3362 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are 3363 * enabled for the RecyclerView depends on the ItemAnimator and whether 3364 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() 3365 * supports item animations}. 3366 * 3367 * @param animator The ItemAnimator being set. If null, no animations will occur 3368 * when changes occur to the items in this RecyclerView. 3369 */ 3370 public void setItemAnimator(@Nullable ItemAnimator animator) { 3371 if (mItemAnimator != null) { 3372 mItemAnimator.endAnimations(); 3373 mItemAnimator.setListener(null); 3374 } 3375 mItemAnimator = animator; 3376 if (mItemAnimator != null) { 3377 mItemAnimator.setListener(mItemAnimatorListener); 3378 } 3379 } 3380 3381 void onEnterLayoutOrScroll() { 3382 mLayoutOrScrollCounter++; 3383 } 3384 3385 void onExitLayoutOrScroll() { 3386 onExitLayoutOrScroll(true); 3387 } 3388 3389 void onExitLayoutOrScroll(boolean enableChangeEvents) { 3390 mLayoutOrScrollCounter--; 3391 if (mLayoutOrScrollCounter < 1) { 3392 if (DEBUG && mLayoutOrScrollCounter < 0) { 3393 throw new IllegalStateException("layout or scroll counter cannot go below zero." 3394 + "Some calls are not matching" + exceptionLabel()); 3395 } 3396 mLayoutOrScrollCounter = 0; 3397 if (enableChangeEvents) { 3398 dispatchContentChangedIfNecessary(); 3399 dispatchPendingImportantForAccessibilityChanges(); 3400 } 3401 } 3402 } 3403 3404 boolean isAccessibilityEnabled() { 3405 return mAccessibilityManager != null && mAccessibilityManager.isEnabled(); 3406 } 3407 3408 private void dispatchContentChangedIfNecessary() { 3409 final int flags = mEatenAccessibilityChangeFlags; 3410 mEatenAccessibilityChangeFlags = 0; 3411 if (flags != 0 && isAccessibilityEnabled()) { 3412 final AccessibilityEvent event = AccessibilityEvent.obtain(); 3413 event.setEventType(AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); 3414 AccessibilityEventCompat.setContentChangeTypes(event, flags); 3415 sendAccessibilityEventUnchecked(event); 3416 } 3417 } 3418 3419 /** 3420 * Returns whether RecyclerView is currently computing a layout. 3421 * <p> 3422 * If this method returns true, it means that RecyclerView is in a lockdown state and any 3423 * attempt to update adapter contents will result in an exception because adapter contents 3424 * cannot be changed while RecyclerView is trying to compute the layout. 3425 * <p> 3426 * It is very unlikely that your code will be running during this state as it is 3427 * called by the framework when a layout traversal happens or RecyclerView starts to scroll 3428 * in response to system events (touch, accessibility etc). 3429 * <p> 3430 * This case may happen if you have some custom logic to change adapter contents in 3431 * response to a View callback (e.g. focus change callback) which might be triggered during a 3432 * layout calculation. In these cases, you should just postpone the change using a Handler or a 3433 * similar mechanism. 3434 * 3435 * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code> 3436 * otherwise 3437 */ 3438 public boolean isComputingLayout() { 3439 return mLayoutOrScrollCounter > 0; 3440 } 3441 3442 /** 3443 * Returns true if an accessibility event should not be dispatched now. This happens when an 3444 * accessibility request arrives while RecyclerView does not have a stable state which is very 3445 * hard to handle for a LayoutManager. Instead, this method records necessary information about 3446 * the event and dispatches a window change event after the critical section is finished. 3447 * 3448 * @return True if the accessibility event should be postponed. 3449 */ 3450 boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) { 3451 if (isComputingLayout()) { 3452 int type = 0; 3453 if (event != null) { 3454 type = AccessibilityEventCompat.getContentChangeTypes(event); 3455 } 3456 if (type == 0) { 3457 type = AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED; 3458 } 3459 mEatenAccessibilityChangeFlags |= type; 3460 return true; 3461 } 3462 return false; 3463 } 3464 3465 @Override 3466 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 3467 if (shouldDeferAccessibilityEvent(event)) { 3468 return; 3469 } 3470 super.sendAccessibilityEventUnchecked(event); 3471 } 3472 3473 /** 3474 * Gets the current ItemAnimator for this RecyclerView. A null return value 3475 * indicates that there is no animator and that item changes will happen without 3476 * any animations. By default, RecyclerView instantiates and 3477 * uses an instance of {@link DefaultItemAnimator}. 3478 * 3479 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur 3480 * when changes occur to the items in this RecyclerView. 3481 */ 3482 @Nullable 3483 public ItemAnimator getItemAnimator() { 3484 return mItemAnimator; 3485 } 3486 3487 /** 3488 * Post a runnable to the next frame to run pending item animations. Only the first such 3489 * request will be posted, governed by the mPostedAnimatorRunner flag. 3490 */ 3491 void postAnimationRunner() { 3492 if (!mPostedAnimatorRunner && mIsAttached) { 3493 ViewCompat.postOnAnimation(this, mItemAnimatorRunner); 3494 mPostedAnimatorRunner = true; 3495 } 3496 } 3497 3498 private boolean predictiveItemAnimationsEnabled() { 3499 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); 3500 } 3501 3502 /** 3503 * Consumes adapter updates and calculates which type of animations we want to run. 3504 * Called in onMeasure and dispatchLayout. 3505 * <p> 3506 * This method may process only the pre-layout state of updates or all of them. 3507 */ 3508 private void processAdapterUpdatesAndSetAnimationFlags() { 3509 if (mDataSetHasChangedAfterLayout) { 3510 // Processing these items have no value since data set changed unexpectedly. 3511 // Instead, we just reset it. 3512 mAdapterHelper.reset(); 3513 if (mDispatchItemsChangedEvent) { 3514 mLayout.onItemsChanged(this); 3515 } 3516 } 3517 // simple animations are a subset of advanced animations (which will cause a 3518 // pre-layout step) 3519 // If layout supports predictive animations, pre-process to decide if we want to run them 3520 if (predictiveItemAnimationsEnabled()) { 3521 mAdapterHelper.preProcess(); 3522 } else { 3523 mAdapterHelper.consumeUpdatesInOnePass(); 3524 } 3525 boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; 3526 mState.mRunSimpleAnimations = mFirstLayoutComplete 3527 && mItemAnimator != null 3528 && (mDataSetHasChangedAfterLayout 3529 || animationTypeSupported 3530 || mLayout.mRequestedSimpleAnimations) 3531 && (!mDataSetHasChangedAfterLayout 3532 || mAdapter.hasStableIds()); 3533 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations 3534 && animationTypeSupported 3535 && !mDataSetHasChangedAfterLayout 3536 && predictiveItemAnimationsEnabled(); 3537 } 3538 3539 /** 3540 * Wrapper around layoutChildren() that handles animating changes caused by layout. 3541 * Animations work on the assumption that there are five different kinds of items 3542 * in play: 3543 * PERSISTENT: items are visible before and after layout 3544 * REMOVED: items were visible before layout and were removed by the app 3545 * ADDED: items did not exist before layout and were added by the app 3546 * DISAPPEARING: items exist in the data set before/after, but changed from 3547 * visible to non-visible in the process of layout (they were moved off 3548 * screen as a side-effect of other changes) 3549 * APPEARING: items exist in the data set before/after, but changed from 3550 * non-visible to visible in the process of layout (they were moved on 3551 * screen as a side-effect of other changes) 3552 * The overall approach figures out what items exist before/after layout and 3553 * infers one of the five above states for each of the items. Then the animations 3554 * are set up accordingly: 3555 * PERSISTENT views are animated via 3556 * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3557 * DISAPPEARING views are animated via 3558 * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3559 * APPEARING views are animated via 3560 * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 3561 * and changed views are animated via 3562 * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. 3563 */ 3564 void dispatchLayout() { 3565 if (mAdapter == null) { 3566 Log.e(TAG, "No adapter attached; skipping layout"); 3567 // leave the state in START 3568 return; 3569 } 3570 if (mLayout == null) { 3571 Log.e(TAG, "No layout manager attached; skipping layout"); 3572 // leave the state in START 3573 return; 3574 } 3575 mState.mIsMeasuring = false; 3576 if (mState.mLayoutStep == State.STEP_START) { 3577 dispatchLayoutStep1(); 3578 mLayout.setExactMeasureSpecsFrom(this); 3579 dispatchLayoutStep2(); 3580 } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() 3581 || mLayout.getHeight() != getHeight()) { 3582 // First 2 steps are done in onMeasure but looks like we have to run again due to 3583 // changed size. 3584 mLayout.setExactMeasureSpecsFrom(this); 3585 dispatchLayoutStep2(); 3586 } else { 3587 // always make sure we sync them (to ensure mode is exact) 3588 mLayout.setExactMeasureSpecsFrom(this); 3589 } 3590 dispatchLayoutStep3(); 3591 } 3592 3593 private void saveFocusInfo() { 3594 View child = null; 3595 if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { 3596 child = getFocusedChild(); 3597 } 3598 3599 final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); 3600 if (focusedVh == null) { 3601 resetFocusInfo(); 3602 } else { 3603 mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; 3604 // mFocusedItemPosition should hold the current adapter position of the previously 3605 // focused item. If the item is removed, we store the previous adapter position of the 3606 // removed item. 3607 mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION 3608 : (focusedVh.isRemoved() ? focusedVh.mOldPosition 3609 : focusedVh.getAdapterPosition()); 3610 mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); 3611 } 3612 } 3613 3614 private void resetFocusInfo() { 3615 mState.mFocusedItemId = NO_ID; 3616 mState.mFocusedItemPosition = NO_POSITION; 3617 mState.mFocusedSubChildId = View.NO_ID; 3618 } 3619 3620 /** 3621 * Finds the best view candidate to request focus on using mFocusedItemPosition index of the 3622 * previously focused item. It first traverses the adapter forward to find a focusable candidate 3623 * and if no such candidate is found, it reverses the focus search direction for the items 3624 * before the mFocusedItemPosition'th index; 3625 * @return The best candidate to request focus on, or null if no such candidate exists. Null 3626 * indicates all the existing adapter items are unfocusable. 3627 */ 3628 @Nullable 3629 private View findNextViewToFocus() { 3630 int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition 3631 : 0; 3632 ViewHolder nextFocus; 3633 final int itemCount = mState.getItemCount(); 3634 for (int i = startFocusSearchIndex; i < itemCount; i++) { 3635 nextFocus = findViewHolderForAdapterPosition(i); 3636 if (nextFocus == null) { 3637 break; 3638 } 3639 if (nextFocus.itemView.hasFocusable()) { 3640 return nextFocus.itemView; 3641 } 3642 } 3643 final int limit = Math.min(itemCount, startFocusSearchIndex); 3644 for (int i = limit - 1; i >= 0; i--) { 3645 nextFocus = findViewHolderForAdapterPosition(i); 3646 if (nextFocus == null) { 3647 return null; 3648 } 3649 if (nextFocus.itemView.hasFocusable()) { 3650 return nextFocus.itemView; 3651 } 3652 } 3653 return null; 3654 } 3655 3656 private void recoverFocusFromState() { 3657 if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() 3658 || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS 3659 || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { 3660 // No-op if either of these cases happens: 3661 // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus 3662 // before its children and is focused (i.e. it already stole the focus away from its 3663 // descendants). 3664 return; 3665 } 3666 // only recover focus if RV itself has the focus or the focused view is hidden 3667 if (!isFocused()) { 3668 final View focusedChild = getFocusedChild(); 3669 if (IGNORE_DETACHED_FOCUSED_CHILD 3670 && (focusedChild.getParent() == null || !focusedChild.hasFocus())) { 3671 // Special handling of API 15-. A focused child can be invalid because mFocus is not 3672 // cleared when the child is detached (mParent = null), 3673 // This happens because clearFocus on API 15- does not invalidate mFocus of its 3674 // parent when this child is detached. 3675 // For API 16+, this is not an issue because requestFocus takes care of clearing the 3676 // prior detached focused child. For API 15- the problem happens in 2 cases because 3677 // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called 3678 // for the current focused item which calls clearChild or 2. when the prior focused 3679 // child is removed, removeDetachedView called in layout step 3 which calls 3680 // clearChild. We should ignore this invalid focused child in all our calculations 3681 // for the next view to receive focus, and apply the focus recovery logic instead. 3682 if (mChildHelper.getChildCount() == 0) { 3683 // No children left. Request focus on the RV itself since one of its children 3684 // was holding focus previously. 3685 requestFocus(); 3686 return; 3687 } 3688 } else if (!mChildHelper.isHidden(focusedChild)) { 3689 // If the currently focused child is hidden, apply the focus recovery logic. 3690 // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. 3691 return; 3692 } 3693 } 3694 ViewHolder focusTarget = null; 3695 // RV first attempts to locate the previously focused item to request focus on using 3696 // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to 3697 // find the next best candidate to request focus on based on mFocusedItemPosition. 3698 if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { 3699 focusTarget = findViewHolderForItemId(mState.mFocusedItemId); 3700 } 3701 View viewToFocus = null; 3702 if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) 3703 || !focusTarget.itemView.hasFocusable()) { 3704 if (mChildHelper.getChildCount() > 0) { 3705 // At this point, RV has focus and either of these conditions are true: 3706 // 1. There's no previously focused item either because RV received focused before 3707 // layout, or the previously focused item was removed, or RV doesn't have stable IDs 3708 // 2. Previous focus child is hidden, or 3. Previous focused child is no longer 3709 // focusable. In either of these cases, we make sure that RV still passes down the 3710 // focus to one of its focusable children using a best-effort algorithm. 3711 viewToFocus = findNextViewToFocus(); 3712 } 3713 } else { 3714 // looks like the focused item has been replaced with another view that represents the 3715 // same item in the adapter. Request focus on that. 3716 viewToFocus = focusTarget.itemView; 3717 } 3718 3719 if (viewToFocus != null) { 3720 if (mState.mFocusedSubChildId != NO_ID) { 3721 View child = viewToFocus.findViewById(mState.mFocusedSubChildId); 3722 if (child != null && child.isFocusable()) { 3723 viewToFocus = child; 3724 } 3725 } 3726 viewToFocus.requestFocus(); 3727 } 3728 } 3729 3730 private int getDeepestFocusedViewWithId(View view) { 3731 int lastKnownId = view.getId(); 3732 while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { 3733 view = ((ViewGroup) view).getFocusedChild(); 3734 final int id = view.getId(); 3735 if (id != View.NO_ID) { 3736 lastKnownId = view.getId(); 3737 } 3738 } 3739 return lastKnownId; 3740 } 3741 3742 final void fillRemainingScrollValues(State state) { 3743 if (getScrollState() == SCROLL_STATE_SETTLING) { 3744 final OverScroller scroller = mViewFlinger.mScroller; 3745 state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX(); 3746 state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY(); 3747 } else { 3748 state.mRemainingScrollHorizontal = 0; 3749 state.mRemainingScrollVertical = 0; 3750 } 3751 } 3752 3753 /** 3754 * The first step of a layout where we; 3755 * - process adapter updates 3756 * - decide which animation should run 3757 * - save information about current views 3758 * - If necessary, run predictive layout and save its information 3759 */ 3760 private void dispatchLayoutStep1() { 3761 mState.assertLayoutStep(State.STEP_START); 3762 fillRemainingScrollValues(mState); 3763 mState.mIsMeasuring = false; 3764 startInterceptRequestLayout(); 3765 mViewInfoStore.clear(); 3766 onEnterLayoutOrScroll(); 3767 processAdapterUpdatesAndSetAnimationFlags(); 3768 saveFocusInfo(); 3769 mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; 3770 mItemsAddedOrRemoved = mItemsChanged = false; 3771 mState.mInPreLayout = mState.mRunPredictiveAnimations; 3772 mState.mItemCount = mAdapter.getItemCount(); 3773 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 3774 3775 if (mState.mRunSimpleAnimations) { 3776 // Step 0: Find out where all non-removed items are, pre-layout 3777 int count = mChildHelper.getChildCount(); 3778 for (int i = 0; i < count; ++i) { 3779 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 3780 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { 3781 continue; 3782 } 3783 final ItemHolderInfo animationInfo = mItemAnimator 3784 .recordPreLayoutInformation(mState, holder, 3785 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), 3786 holder.getUnmodifiedPayloads()); 3787 mViewInfoStore.addToPreLayout(holder, animationInfo); 3788 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() 3789 && !holder.shouldIgnore() && !holder.isInvalid()) { 3790 long key = getChangedHolderKey(holder); 3791 // This is NOT the only place where a ViewHolder is added to old change holders 3792 // list. There is another case where: 3793 // * A VH is currently hidden but not deleted 3794 // * The hidden item is changed in the adapter 3795 // * Layout manager decides to layout the item in the pre-Layout pass (step1) 3796 // When this case is detected, RV will un-hide that view and add to the old 3797 // change holders list. 3798 mViewInfoStore.addToOldChangeHolders(key, holder); 3799 } 3800 } 3801 } 3802 if (mState.mRunPredictiveAnimations) { 3803 // Step 1: run prelayout: This will use the old positions of items. The layout manager 3804 // is expected to layout everything, even removed items (though not to add removed 3805 // items back to the container). This gives the pre-layout position of APPEARING views 3806 // which come into existence as part of the real layout. 3807 3808 // Save old positions so that LayoutManager can run its mapping logic. 3809 saveOldPositions(); 3810 final boolean didStructureChange = mState.mStructureChanged; 3811 mState.mStructureChanged = false; 3812 // temporarily disable flag because we are asking for previous layout 3813 mLayout.onLayoutChildren(mRecycler, mState); 3814 mState.mStructureChanged = didStructureChange; 3815 3816 for (int i = 0; i < mChildHelper.getChildCount(); ++i) { 3817 final View child = mChildHelper.getChildAt(i); 3818 final ViewHolder viewHolder = getChildViewHolderInt(child); 3819 if (viewHolder.shouldIgnore()) { 3820 continue; 3821 } 3822 if (!mViewInfoStore.isInPreLayout(viewHolder)) { 3823 int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); 3824 boolean wasHidden = viewHolder 3825 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 3826 if (!wasHidden) { 3827 flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 3828 } 3829 final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( 3830 mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); 3831 if (wasHidden) { 3832 recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); 3833 } else { 3834 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 3835 } 3836 } 3837 } 3838 // we don't process disappearing list because they may re-appear in post layout pass. 3839 clearOldPositions(); 3840 } else { 3841 clearOldPositions(); 3842 } 3843 onExitLayoutOrScroll(); 3844 stopInterceptRequestLayout(false); 3845 mState.mLayoutStep = State.STEP_LAYOUT; 3846 } 3847 3848 /** 3849 * The second layout step where we do the actual layout of the views for the final state. 3850 * This step might be run multiple times if necessary (e.g. measure). 3851 */ 3852 private void dispatchLayoutStep2() { 3853 startInterceptRequestLayout(); 3854 onEnterLayoutOrScroll(); 3855 mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); 3856 mAdapterHelper.consumeUpdatesInOnePass(); 3857 mState.mItemCount = mAdapter.getItemCount(); 3858 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; 3859 3860 // Step 2: Run layout 3861 mState.mInPreLayout = false; 3862 mLayout.onLayoutChildren(mRecycler, mState); 3863 3864 mState.mStructureChanged = false; 3865 mPendingSavedState = null; 3866 3867 // onLayoutChildren may have caused client code to disable item animations; re-check 3868 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; 3869 mState.mLayoutStep = State.STEP_ANIMATIONS; 3870 onExitLayoutOrScroll(); 3871 stopInterceptRequestLayout(false); 3872 } 3873 3874 /** 3875 * The final step of the layout where we save the information about views for animations, 3876 * trigger animations and do any necessary cleanup. 3877 */ 3878 private void dispatchLayoutStep3() { 3879 mState.assertLayoutStep(State.STEP_ANIMATIONS); 3880 startInterceptRequestLayout(); 3881 onEnterLayoutOrScroll(); 3882 mState.mLayoutStep = State.STEP_START; 3883 if (mState.mRunSimpleAnimations) { 3884 // Step 3: Find out where things are now, and process change animations. 3885 // traverse list in reverse because we may call animateChange in the loop which may 3886 // remove the target view holder. 3887 for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { 3888 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 3889 if (holder.shouldIgnore()) { 3890 continue; 3891 } 3892 long key = getChangedHolderKey(holder); 3893 final ItemHolderInfo animationInfo = mItemAnimator 3894 .recordPostLayoutInformation(mState, holder); 3895 ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); 3896 if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { 3897 // run a change animation 3898 3899 // If an Item is CHANGED but the updated version is disappearing, it creates 3900 // a conflicting case. 3901 // Since a view that is marked as disappearing is likely to be going out of 3902 // bounds, we run a change animation. Both views will be cleaned automatically 3903 // once their animations finish. 3904 // On the other hand, if it is the same view holder instance, we run a 3905 // disappearing animation instead because we are not going to rebind the updated 3906 // VH unless it is enforced by the layout manager. 3907 final boolean oldDisappearing = mViewInfoStore.isDisappearing( 3908 oldChangeViewHolder); 3909 final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); 3910 if (oldDisappearing && oldChangeViewHolder == holder) { 3911 // run disappear animation instead of change 3912 mViewInfoStore.addToPostLayout(holder, animationInfo); 3913 } else { 3914 final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( 3915 oldChangeViewHolder); 3916 // we add and remove so that any post info is merged. 3917 mViewInfoStore.addToPostLayout(holder, animationInfo); 3918 ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); 3919 if (preInfo == null) { 3920 handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); 3921 } else { 3922 animateChange(oldChangeViewHolder, holder, preInfo, postInfo, 3923 oldDisappearing, newDisappearing); 3924 } 3925 } 3926 } else { 3927 mViewInfoStore.addToPostLayout(holder, animationInfo); 3928 } 3929 } 3930 3931 // Step 4: Process view info lists and trigger animations 3932 mViewInfoStore.process(mViewInfoProcessCallback); 3933 } 3934 3935 mLayout.removeAndRecycleScrapInt(mRecycler); 3936 mState.mPreviousLayoutItemCount = mState.mItemCount; 3937 mDataSetHasChangedAfterLayout = false; 3938 mDispatchItemsChangedEvent = false; 3939 mState.mRunSimpleAnimations = false; 3940 3941 mState.mRunPredictiveAnimations = false; 3942 mLayout.mRequestedSimpleAnimations = false; 3943 if (mRecycler.mChangedScrap != null) { 3944 mRecycler.mChangedScrap.clear(); 3945 } 3946 if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { 3947 // Initial prefetch has expanded cache, so reset until next prefetch. 3948 // This prevents initial prefetches from expanding the cache permanently. 3949 mLayout.mPrefetchMaxCountObserved = 0; 3950 mLayout.mPrefetchMaxObservedInInitialPrefetch = false; 3951 mRecycler.updateViewCacheSize(); 3952 } 3953 3954 mLayout.onLayoutCompleted(mState); 3955 onExitLayoutOrScroll(); 3956 stopInterceptRequestLayout(false); 3957 mViewInfoStore.clear(); 3958 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { 3959 dispatchOnScrolled(0, 0); 3960 } 3961 recoverFocusFromState(); 3962 resetFocusInfo(); 3963 } 3964 3965 /** 3966 * This handles the case where there is an unexpected VH missing in the pre-layout map. 3967 * <p> 3968 * We might be able to detect the error in the application which will help the developer to 3969 * resolve the issue. 3970 * <p> 3971 * If it is not an expected error, we at least print an error to notify the developer and ignore 3972 * the animation. 3973 * 3974 * https://code.google.com/p/android/issues/detail?id=193958 3975 * 3976 * @param key The change key 3977 * @param holder Current ViewHolder 3978 * @param oldChangeViewHolder Changed ViewHolder 3979 */ 3980 private void handleMissingPreInfoForChangeError(long key, 3981 ViewHolder holder, ViewHolder oldChangeViewHolder) { 3982 // check if two VH have the same key, if so, print that as an error 3983 final int childCount = mChildHelper.getChildCount(); 3984 for (int i = 0; i < childCount; i++) { 3985 View view = mChildHelper.getChildAt(i); 3986 ViewHolder other = getChildViewHolderInt(view); 3987 if (other == holder) { 3988 continue; 3989 } 3990 final long otherKey = getChangedHolderKey(other); 3991 if (otherKey == key) { 3992 if (mAdapter != null && mAdapter.hasStableIds()) { 3993 throw new IllegalStateException("Two different ViewHolders have the same stable" 3994 + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" 3995 + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 3996 + exceptionLabel()); 3997 } else { 3998 throw new IllegalStateException("Two different ViewHolders have the same change" 3999 + " ID. This might happen due to inconsistent Adapter update events or" 4000 + " if the LayoutManager lays out the same View multiple times." 4001 + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 4002 + exceptionLabel()); 4003 } 4004 } 4005 } 4006 // Very unlikely to happen but if it does, notify the developer. 4007 Log.e(TAG, "Problem while matching changed view holders with the new" 4008 + "ones. The pre-layout information for the change holder " + oldChangeViewHolder 4009 + " cannot be found but it is necessary for " + holder + exceptionLabel()); 4010 } 4011 4012 /** 4013 * Records the animation information for a view holder that was bounced from hidden list. It 4014 * also clears the bounce back flag. 4015 */ 4016 void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, 4017 ItemHolderInfo animationInfo) { 4018 // looks like this view bounced back from hidden list! 4019 viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 4020 if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() 4021 && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { 4022 long key = getChangedHolderKey(viewHolder); 4023 mViewInfoStore.addToOldChangeHolders(key, viewHolder); 4024 } 4025 mViewInfoStore.addToPreLayout(viewHolder, animationInfo); 4026 } 4027 4028 private void findMinMaxChildLayoutPositions(int[] into) { 4029 final int count = mChildHelper.getChildCount(); 4030 if (count == 0) { 4031 into[0] = NO_POSITION; 4032 into[1] = NO_POSITION; 4033 return; 4034 } 4035 int minPositionPreLayout = Integer.MAX_VALUE; 4036 int maxPositionPreLayout = Integer.MIN_VALUE; 4037 for (int i = 0; i < count; ++i) { 4038 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 4039 if (holder.shouldIgnore()) { 4040 continue; 4041 } 4042 final int pos = holder.getLayoutPosition(); 4043 if (pos < minPositionPreLayout) { 4044 minPositionPreLayout = pos; 4045 } 4046 if (pos > maxPositionPreLayout) { 4047 maxPositionPreLayout = pos; 4048 } 4049 } 4050 into[0] = minPositionPreLayout; 4051 into[1] = maxPositionPreLayout; 4052 } 4053 4054 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { 4055 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 4056 return mMinMaxLayoutPositions[0] != minPositionPreLayout 4057 || mMinMaxLayoutPositions[1] != maxPositionPreLayout; 4058 } 4059 4060 @Override 4061 protected void removeDetachedView(View child, boolean animate) { 4062 ViewHolder vh = getChildViewHolderInt(child); 4063 if (vh != null) { 4064 if (vh.isTmpDetached()) { 4065 vh.clearTmpDetachFlag(); 4066 } else if (!vh.shouldIgnore()) { 4067 throw new IllegalArgumentException("Called removeDetachedView with a view which" 4068 + " is not flagged as tmp detached." + vh + exceptionLabel()); 4069 } 4070 } 4071 4072 // Clear any android.view.animation.Animation that may prevent the item from 4073 // detaching when being removed. If a child is re-added before the 4074 // lazy detach occurs, it will receive invalid attach/detach sequencing. 4075 child.clearAnimation(); 4076 4077 dispatchChildDetached(child); 4078 super.removeDetachedView(child, animate); 4079 } 4080 4081 /** 4082 * Returns a unique key to be used while handling change animations. 4083 * It might be child's position or stable id depending on the adapter type. 4084 */ 4085 long getChangedHolderKey(ViewHolder holder) { 4086 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; 4087 } 4088 4089 void animateAppearance(@NonNull ViewHolder itemHolder, 4090 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { 4091 itemHolder.setIsRecyclable(false); 4092 if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { 4093 postAnimationRunner(); 4094 } 4095 } 4096 4097 void animateDisappearance(@NonNull ViewHolder holder, 4098 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { 4099 addAnimatingView(holder); 4100 holder.setIsRecyclable(false); 4101 if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { 4102 postAnimationRunner(); 4103 } 4104 } 4105 4106 private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, 4107 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, 4108 boolean oldHolderDisappearing, boolean newHolderDisappearing) { 4109 oldHolder.setIsRecyclable(false); 4110 if (oldHolderDisappearing) { 4111 addAnimatingView(oldHolder); 4112 } 4113 if (oldHolder != newHolder) { 4114 if (newHolderDisappearing) { 4115 addAnimatingView(newHolder); 4116 } 4117 oldHolder.mShadowedHolder = newHolder; 4118 // old holder should disappear after animation ends 4119 addAnimatingView(oldHolder); 4120 mRecycler.unscrapView(oldHolder); 4121 newHolder.setIsRecyclable(false); 4122 newHolder.mShadowingHolder = oldHolder; 4123 } 4124 if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { 4125 postAnimationRunner(); 4126 } 4127 } 4128 4129 @Override 4130 protected void onLayout(boolean changed, int l, int t, int r, int b) { 4131 TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); 4132 dispatchLayout(); 4133 TraceCompat.endSection(); 4134 mFirstLayoutComplete = true; 4135 } 4136 4137 @Override 4138 public void requestLayout() { 4139 if (mInterceptRequestLayoutDepth == 0 && !mLayoutFrozen) { 4140 super.requestLayout(); 4141 } else { 4142 mLayoutWasDefered = true; 4143 } 4144 } 4145 4146 void markItemDecorInsetsDirty() { 4147 final int childCount = mChildHelper.getUnfilteredChildCount(); 4148 for (int i = 0; i < childCount; i++) { 4149 final View child = mChildHelper.getUnfilteredChildAt(i); 4150 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 4151 } 4152 mRecycler.markItemDecorInsetsDirty(); 4153 } 4154 4155 @Override 4156 public void draw(Canvas c) { 4157 super.draw(c); 4158 4159 final int count = mItemDecorations.size(); 4160 for (int i = 0; i < count; i++) { 4161 mItemDecorations.get(i).onDrawOver(c, this, mState); 4162 } 4163 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we 4164 // need find children closest to edges. Not sure if it is worth the effort. 4165 boolean needsInvalidate = false; 4166 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 4167 final int restore = c.save(); 4168 final int padding = mClipToPadding ? getPaddingBottom() : 0; 4169 c.rotate(270); 4170 c.translate(-getHeight() + padding, 0); 4171 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 4172 c.restoreToCount(restore); 4173 } 4174 if (mTopGlow != null && !mTopGlow.isFinished()) { 4175 final int restore = c.save(); 4176 if (mClipToPadding) { 4177 c.translate(getPaddingLeft(), getPaddingTop()); 4178 } 4179 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 4180 c.restoreToCount(restore); 4181 } 4182 if (mRightGlow != null && !mRightGlow.isFinished()) { 4183 final int restore = c.save(); 4184 final int width = getWidth(); 4185 final int padding = mClipToPadding ? getPaddingTop() : 0; 4186 c.rotate(90); 4187 c.translate(-padding, -width); 4188 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 4189 c.restoreToCount(restore); 4190 } 4191 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 4192 final int restore = c.save(); 4193 c.rotate(180); 4194 if (mClipToPadding) { 4195 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); 4196 } else { 4197 c.translate(-getWidth(), -getHeight()); 4198 } 4199 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 4200 c.restoreToCount(restore); 4201 } 4202 4203 // If some views are animating, ItemDecorators are likely to move/change with them. 4204 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's 4205 // display lists are not invalidated. 4206 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 4207 && mItemAnimator.isRunning()) { 4208 needsInvalidate = true; 4209 } 4210 4211 if (needsInvalidate) { 4212 ViewCompat.postInvalidateOnAnimation(this); 4213 } 4214 } 4215 4216 @Override 4217 public void onDraw(Canvas c) { 4218 super.onDraw(c); 4219 4220 final int count = mItemDecorations.size(); 4221 for (int i = 0; i < count; i++) { 4222 mItemDecorations.get(i).onDraw(c, this, mState); 4223 } 4224 } 4225 4226 @Override 4227 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 4228 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 4229 } 4230 4231 @Override 4232 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 4233 if (mLayout == null) { 4234 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4235 } 4236 return mLayout.generateDefaultLayoutParams(); 4237 } 4238 4239 @Override 4240 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 4241 if (mLayout == null) { 4242 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4243 } 4244 return mLayout.generateLayoutParams(getContext(), attrs); 4245 } 4246 4247 @Override 4248 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 4249 if (mLayout == null) { 4250 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 4251 } 4252 return mLayout.generateLayoutParams(p); 4253 } 4254 4255 /** 4256 * Returns true if RecyclerView is currently running some animations. 4257 * <p> 4258 * If you want to be notified when animations are finished, use 4259 * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}. 4260 * 4261 * @return True if there are some item animations currently running or waiting to be started. 4262 */ 4263 public boolean isAnimating() { 4264 return mItemAnimator != null && mItemAnimator.isRunning(); 4265 } 4266 4267 void saveOldPositions() { 4268 final int childCount = mChildHelper.getUnfilteredChildCount(); 4269 for (int i = 0; i < childCount; i++) { 4270 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4271 if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { 4272 throw new IllegalStateException("view holder cannot have position -1 unless it" 4273 + " is removed" + exceptionLabel()); 4274 } 4275 if (!holder.shouldIgnore()) { 4276 holder.saveOldPosition(); 4277 } 4278 } 4279 } 4280 4281 void clearOldPositions() { 4282 final int childCount = mChildHelper.getUnfilteredChildCount(); 4283 for (int i = 0; i < childCount; i++) { 4284 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4285 if (!holder.shouldIgnore()) { 4286 holder.clearOldPosition(); 4287 } 4288 } 4289 mRecycler.clearOldPositions(); 4290 } 4291 4292 void offsetPositionRecordsForMove(int from, int to) { 4293 final int childCount = mChildHelper.getUnfilteredChildCount(); 4294 final int start, end, inBetweenOffset; 4295 if (from < to) { 4296 start = from; 4297 end = to; 4298 inBetweenOffset = -1; 4299 } else { 4300 start = to; 4301 end = from; 4302 inBetweenOffset = 1; 4303 } 4304 4305 for (int i = 0; i < childCount; i++) { 4306 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4307 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 4308 continue; 4309 } 4310 if (DEBUG) { 4311 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " 4312 + holder); 4313 } 4314 if (holder.mPosition == from) { 4315 holder.offsetPosition(to - from, false); 4316 } else { 4317 holder.offsetPosition(inBetweenOffset, false); 4318 } 4319 4320 mState.mStructureChanged = true; 4321 } 4322 mRecycler.offsetPositionRecordsForMove(from, to); 4323 requestLayout(); 4324 } 4325 4326 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 4327 final int childCount = mChildHelper.getUnfilteredChildCount(); 4328 for (int i = 0; i < childCount; i++) { 4329 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4330 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { 4331 if (DEBUG) { 4332 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " 4333 + holder + " now at position " + (holder.mPosition + itemCount)); 4334 } 4335 holder.offsetPosition(itemCount, false); 4336 mState.mStructureChanged = true; 4337 } 4338 } 4339 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 4340 requestLayout(); 4341 } 4342 4343 void offsetPositionRecordsForRemove(int positionStart, int itemCount, 4344 boolean applyToPreLayout) { 4345 final int positionEnd = positionStart + itemCount; 4346 final int childCount = mChildHelper.getUnfilteredChildCount(); 4347 for (int i = 0; i < childCount; i++) { 4348 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4349 if (holder != null && !holder.shouldIgnore()) { 4350 if (holder.mPosition >= positionEnd) { 4351 if (DEBUG) { 4352 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 4353 + " holder " + holder + " now at position " 4354 + (holder.mPosition - itemCount)); 4355 } 4356 holder.offsetPosition(-itemCount, applyToPreLayout); 4357 mState.mStructureChanged = true; 4358 } else if (holder.mPosition >= positionStart) { 4359 if (DEBUG) { 4360 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 4361 + " holder " + holder + " now REMOVED"); 4362 } 4363 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, 4364 applyToPreLayout); 4365 mState.mStructureChanged = true; 4366 } 4367 } 4368 } 4369 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); 4370 requestLayout(); 4371 } 4372 4373 /** 4374 * Rebind existing views for the given range, or create as needed. 4375 * 4376 * @param positionStart Adapter position to start at 4377 * @param itemCount Number of views that must explicitly be rebound 4378 */ 4379 void viewRangeUpdate(int positionStart, int itemCount, Object payload) { 4380 final int childCount = mChildHelper.getUnfilteredChildCount(); 4381 final int positionEnd = positionStart + itemCount; 4382 4383 for (int i = 0; i < childCount; i++) { 4384 final View child = mChildHelper.getUnfilteredChildAt(i); 4385 final ViewHolder holder = getChildViewHolderInt(child); 4386 if (holder == null || holder.shouldIgnore()) { 4387 continue; 4388 } 4389 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 4390 // We re-bind these view holders after pre-processing is complete so that 4391 // ViewHolders have their final positions assigned. 4392 holder.addFlags(ViewHolder.FLAG_UPDATE); 4393 holder.addChangePayload(payload); 4394 // lp cannot be null since we get ViewHolder from it. 4395 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 4396 } 4397 } 4398 mRecycler.viewRangeUpdate(positionStart, itemCount); 4399 } 4400 4401 boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { 4402 return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, 4403 viewHolder.getUnmodifiedPayloads()); 4404 } 4405 4406 /** 4407 * Processes the fact that, as far as we can tell, the data set has completely changed. 4408 * 4409 * <ul> 4410 * <li>Once layout occurs, all attached items should be discarded or animated. 4411 * <li>Attached items are labeled as invalid. 4412 * <li>Because items may still be prefetched between a "data set completely changed" 4413 * event and a layout event, all cached items are discarded. 4414 * </ul> 4415 * 4416 * @param dispatchItemsChanged Whether to call 4417 * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout. 4418 */ 4419 void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { 4420 mDispatchItemsChangedEvent |= dispatchItemsChanged; 4421 mDataSetHasChangedAfterLayout = true; 4422 markKnownViewsInvalid(); 4423 } 4424 4425 /** 4426 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 4427 * data change event. 4428 */ 4429 void markKnownViewsInvalid() { 4430 final int childCount = mChildHelper.getUnfilteredChildCount(); 4431 for (int i = 0; i < childCount; i++) { 4432 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4433 if (holder != null && !holder.shouldIgnore()) { 4434 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 4435 } 4436 } 4437 markItemDecorInsetsDirty(); 4438 mRecycler.markKnownViewsInvalid(); 4439 } 4440 4441 /** 4442 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method 4443 * will trigger a {@link #requestLayout()} call. 4444 */ 4445 public void invalidateItemDecorations() { 4446 if (mItemDecorations.size() == 0) { 4447 return; 4448 } 4449 if (mLayout != null) { 4450 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" 4451 + " or layout"); 4452 } 4453 markItemDecorInsetsDirty(); 4454 requestLayout(); 4455 } 4456 4457 /** 4458 * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's 4459 * focus even if the View representing the Item is replaced during a layout calculation. 4460 * <p> 4461 * By default, this value is {@code true}. 4462 * 4463 * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses 4464 * focus. 4465 * 4466 * @see #setPreserveFocusAfterLayout(boolean) 4467 */ 4468 public boolean getPreserveFocusAfterLayout() { 4469 return mPreserveFocusAfterLayout; 4470 } 4471 4472 /** 4473 * Set whether the RecyclerView should try to keep the same Item focused after a layout 4474 * calculation or not. 4475 * <p> 4476 * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, 4477 * views may lose focus during a layout calculation as their state changes or they are replaced 4478 * with another view due to type change or animation. In these cases, RecyclerView can request 4479 * focus on the new view automatically. 4480 * 4481 * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a 4482 * layout calculations. Defaults to true. 4483 * 4484 * @see #getPreserveFocusAfterLayout() 4485 */ 4486 public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { 4487 mPreserveFocusAfterLayout = preserveFocusAfterLayout; 4488 } 4489 4490 /** 4491 * Retrieve the {@link ViewHolder} for the given child view. 4492 * 4493 * @param child Child of this RecyclerView to query for its ViewHolder 4494 * @return The child view's ViewHolder 4495 */ 4496 public ViewHolder getChildViewHolder(@NonNull View child) { 4497 final ViewParent parent = child.getParent(); 4498 if (parent != null && parent != this) { 4499 throw new IllegalArgumentException("View " + child + " is not a direct child of " 4500 + this); 4501 } 4502 return getChildViewHolderInt(child); 4503 } 4504 4505 /** 4506 * Traverses the ancestors of the given view and returns the item view that contains it and 4507 * also a direct child of the RecyclerView. This returned view can be used to get the 4508 * ViewHolder by calling {@link #getChildViewHolder(View)}. 4509 * 4510 * @param view The view that is a descendant of the RecyclerView. 4511 * 4512 * @return The direct child of the RecyclerView which contains the given view or null if the 4513 * provided view is not a descendant of this RecyclerView. 4514 * 4515 * @see #getChildViewHolder(View) 4516 * @see #findContainingViewHolder(View) 4517 */ 4518 @Nullable 4519 public View findContainingItemView(@NonNull View view) { 4520 ViewParent parent = view.getParent(); 4521 while (parent != null && parent != this && parent instanceof View) { 4522 view = (View) parent; 4523 parent = view.getParent(); 4524 } 4525 return parent == this ? view : null; 4526 } 4527 4528 /** 4529 * Returns the ViewHolder that contains the given view. 4530 * 4531 * @param view The view that is a descendant of the RecyclerView. 4532 * 4533 * @return The ViewHolder that contains the given view or null if the provided view is not a 4534 * descendant of this RecyclerView. 4535 */ 4536 @Nullable 4537 public ViewHolder findContainingViewHolder(@NonNull View view) { 4538 View itemView = findContainingItemView(view); 4539 return itemView == null ? null : getChildViewHolder(itemView); 4540 } 4541 4542 4543 static ViewHolder getChildViewHolderInt(View child) { 4544 if (child == null) { 4545 return null; 4546 } 4547 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 4548 } 4549 4550 /** 4551 * @deprecated use {@link #getChildAdapterPosition(View)} or 4552 * {@link #getChildLayoutPosition(View)}. 4553 */ 4554 @Deprecated 4555 public int getChildPosition(@NonNull View child) { 4556 return getChildAdapterPosition(child); 4557 } 4558 4559 /** 4560 * Return the adapter position that the given child view corresponds to. 4561 * 4562 * @param child Child View to query 4563 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 4564 */ 4565 public int getChildAdapterPosition(@NonNull View child) { 4566 final ViewHolder holder = getChildViewHolderInt(child); 4567 return holder != null ? holder.getAdapterPosition() : NO_POSITION; 4568 } 4569 4570 /** 4571 * Return the adapter position of the given child view as of the latest completed layout pass. 4572 * <p> 4573 * This position may not be equal to Item's adapter position if there are pending changes 4574 * in the adapter which have not been reflected to the layout yet. 4575 * 4576 * @param child Child View to query 4577 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if 4578 * the View is representing a removed item. 4579 */ 4580 public int getChildLayoutPosition(@NonNull View child) { 4581 final ViewHolder holder = getChildViewHolderInt(child); 4582 return holder != null ? holder.getLayoutPosition() : NO_POSITION; 4583 } 4584 4585 /** 4586 * Return the stable item id that the given child view corresponds to. 4587 * 4588 * @param child Child View to query 4589 * @return Item id corresponding to the given view or {@link #NO_ID} 4590 */ 4591 public long getChildItemId(@NonNull View child) { 4592 if (mAdapter == null || !mAdapter.hasStableIds()) { 4593 return NO_ID; 4594 } 4595 final ViewHolder holder = getChildViewHolderInt(child); 4596 return holder != null ? holder.getItemId() : NO_ID; 4597 } 4598 4599 /** 4600 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or 4601 * {@link #findViewHolderForAdapterPosition(int)} 4602 */ 4603 @Deprecated 4604 public ViewHolder findViewHolderForPosition(int position) { 4605 return findViewHolderForPosition(position, false); 4606 } 4607 4608 /** 4609 * Return the ViewHolder for the item in the given position of the data set as of the latest 4610 * layout pass. 4611 * <p> 4612 * This method checks only the children of RecyclerView. If the item at the given 4613 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 4614 * <p> 4615 * Note that when Adapter contents change, ViewHolder positions are not updated until the 4616 * next layout calculation. If there are pending adapter updates, the return value of this 4617 * method may not match your adapter contents. You can use 4618 * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. 4619 * <p> 4620 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 4621 * with the same layout position representing the same Item. In this case, the updated 4622 * ViewHolder will be returned. 4623 * 4624 * @param position The position of the item in the data set of the adapter 4625 * @return The ViewHolder at <code>position</code> or null if there is no such item 4626 */ 4627 public ViewHolder findViewHolderForLayoutPosition(int position) { 4628 return findViewHolderForPosition(position, false); 4629 } 4630 4631 /** 4632 * Return the ViewHolder for the item in the given position of the data set. Unlike 4633 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending 4634 * adapter changes that may not be reflected to the layout yet. On the other hand, if 4635 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been 4636 * calculated yet, this method will return <code>null</code> since the new positions of views 4637 * are unknown until the layout is calculated. 4638 * <p> 4639 * This method checks only the children of RecyclerView. If the item at the given 4640 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 4641 * <p> 4642 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 4643 * representing the same Item. In this case, the updated ViewHolder will be returned. 4644 * 4645 * @param position The position of the item in the data set of the adapter 4646 * @return The ViewHolder at <code>position</code> or null if there is no such item 4647 */ 4648 public ViewHolder findViewHolderForAdapterPosition(int position) { 4649 if (mDataSetHasChangedAfterLayout) { 4650 return null; 4651 } 4652 final int childCount = mChildHelper.getUnfilteredChildCount(); 4653 // hidden VHs are not preferred but if that is the only one we find, we rather return it 4654 ViewHolder hidden = null; 4655 for (int i = 0; i < childCount; i++) { 4656 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4657 if (holder != null && !holder.isRemoved() 4658 && getAdapterPositionFor(holder) == position) { 4659 if (mChildHelper.isHidden(holder.itemView)) { 4660 hidden = holder; 4661 } else { 4662 return holder; 4663 } 4664 } 4665 } 4666 return hidden; 4667 } 4668 4669 ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { 4670 final int childCount = mChildHelper.getUnfilteredChildCount(); 4671 ViewHolder hidden = null; 4672 for (int i = 0; i < childCount; i++) { 4673 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4674 if (holder != null && !holder.isRemoved()) { 4675 if (checkNewPosition) { 4676 if (holder.mPosition != position) { 4677 continue; 4678 } 4679 } else if (holder.getLayoutPosition() != position) { 4680 continue; 4681 } 4682 if (mChildHelper.isHidden(holder.itemView)) { 4683 hidden = holder; 4684 } else { 4685 return holder; 4686 } 4687 } 4688 } 4689 // This method should not query cached views. It creates a problem during adapter updates 4690 // when we are dealing with already laid out views. Also, for the public method, it is more 4691 // reasonable to return null if position is not laid out. 4692 return hidden; 4693 } 4694 4695 /** 4696 * Return the ViewHolder for the item with the given id. The RecyclerView must 4697 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 4698 * return a non-null value. 4699 * <p> 4700 * This method checks only the children of RecyclerView. If the item with the given 4701 * <code>id</code> is not laid out, it <em>will not</em> create a new one. 4702 * 4703 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the 4704 * same id. In this case, the updated ViewHolder will be returned. 4705 * 4706 * @param id The id for the requested item 4707 * @return The ViewHolder with the given <code>id</code> or null if there is no such item 4708 */ 4709 public ViewHolder findViewHolderForItemId(long id) { 4710 if (mAdapter == null || !mAdapter.hasStableIds()) { 4711 return null; 4712 } 4713 final int childCount = mChildHelper.getUnfilteredChildCount(); 4714 ViewHolder hidden = null; 4715 for (int i = 0; i < childCount; i++) { 4716 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 4717 if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { 4718 if (mChildHelper.isHidden(holder.itemView)) { 4719 hidden = holder; 4720 } else { 4721 return holder; 4722 } 4723 } 4724 } 4725 return hidden; 4726 } 4727 4728 /** 4729 * Find the topmost view under the given point. 4730 * 4731 * @param x Horizontal position in pixels to search 4732 * @param y Vertical position in pixels to search 4733 * @return The child view under (x, y) or null if no matching child is found 4734 */ 4735 @Nullable 4736 public View findChildViewUnder(float x, float y) { 4737 final int count = mChildHelper.getChildCount(); 4738 for (int i = count - 1; i >= 0; i--) { 4739 final View child = mChildHelper.getChildAt(i); 4740 final float translationX = child.getTranslationX(); 4741 final float translationY = child.getTranslationY(); 4742 if (x >= child.getLeft() + translationX 4743 && x <= child.getRight() + translationX 4744 && y >= child.getTop() + translationY 4745 && y <= child.getBottom() + translationY) { 4746 return child; 4747 } 4748 } 4749 return null; 4750 } 4751 4752 @Override 4753 public boolean drawChild(Canvas canvas, View child, long drawingTime) { 4754 return super.drawChild(canvas, child, drawingTime); 4755 } 4756 4757 /** 4758 * Offset the bounds of all child views by <code>dy</code> pixels. 4759 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 4760 * 4761 * @param dy Vertical pixel offset to apply to the bounds of all child views 4762 */ 4763 public void offsetChildrenVertical(@Px int dy) { 4764 final int childCount = mChildHelper.getChildCount(); 4765 for (int i = 0; i < childCount; i++) { 4766 mChildHelper.getChildAt(i).offsetTopAndBottom(dy); 4767 } 4768 } 4769 4770 /** 4771 * Called when an item view is attached to this RecyclerView. 4772 * 4773 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 4774 * of child views as they become attached. This will be called before a 4775 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 4776 * changes.</p> 4777 * 4778 * @param child Child view that is now attached to this RecyclerView and its associated window 4779 */ 4780 public void onChildAttachedToWindow(@NonNull View child) { 4781 } 4782 4783 /** 4784 * Called when an item view is detached from this RecyclerView. 4785 * 4786 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 4787 * of child views as they become detached. This will be called as a 4788 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 4789 * 4790 * @param child Child view that is now detached from this RecyclerView and its associated window 4791 */ 4792 public void onChildDetachedFromWindow(@NonNull View child) { 4793 } 4794 4795 /** 4796 * Offset the bounds of all child views by <code>dx</code> pixels. 4797 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 4798 * 4799 * @param dx Horizontal pixel offset to apply to the bounds of all child views 4800 */ 4801 public void offsetChildrenHorizontal(@Px int dx) { 4802 final int childCount = mChildHelper.getChildCount(); 4803 for (int i = 0; i < childCount; i++) { 4804 mChildHelper.getChildAt(i).offsetLeftAndRight(dx); 4805 } 4806 } 4807 4808 /** 4809 * Returns the bounds of the view including its decoration and margins. 4810 * 4811 * @param view The view element to check 4812 * @param outBounds A rect that will receive the bounds of the element including its 4813 * decoration and margins. 4814 */ 4815 public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { 4816 getDecoratedBoundsWithMarginsInt(view, outBounds); 4817 } 4818 4819 static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { 4820 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 4821 final Rect insets = lp.mDecorInsets; 4822 outBounds.set(view.getLeft() - insets.left - lp.leftMargin, 4823 view.getTop() - insets.top - lp.topMargin, 4824 view.getRight() + insets.right + lp.rightMargin, 4825 view.getBottom() + insets.bottom + lp.bottomMargin); 4826 } 4827 4828 Rect getItemDecorInsetsForChild(View child) { 4829 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 4830 if (!lp.mInsetsDirty) { 4831 return lp.mDecorInsets; 4832 } 4833 4834 if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { 4835 // changed/invalid items should not be updated until they are rebound. 4836 return lp.mDecorInsets; 4837 } 4838 final Rect insets = lp.mDecorInsets; 4839 insets.set(0, 0, 0, 0); 4840 final int decorCount = mItemDecorations.size(); 4841 for (int i = 0; i < decorCount; i++) { 4842 mTempRect.set(0, 0, 0, 0); 4843 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 4844 insets.left += mTempRect.left; 4845 insets.top += mTempRect.top; 4846 insets.right += mTempRect.right; 4847 insets.bottom += mTempRect.bottom; 4848 } 4849 lp.mInsetsDirty = false; 4850 return insets; 4851 } 4852 4853 /** 4854 * Called when the scroll position of this RecyclerView changes. Subclasses should use 4855 * this method to respond to scrolling within the adapter's data set instead of an explicit 4856 * listener. 4857 * 4858 * <p>This method will always be invoked before listeners. If a subclass needs to perform 4859 * any additional upkeep or bookkeeping after scrolling but before listeners run, 4860 * this is a good place to do so.</p> 4861 * 4862 * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives 4863 * the distance scrolled in either direction within the adapter's data set instead of absolute 4864 * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from 4865 * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive 4866 * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which 4867 * do not correspond to the data set scroll position. However, some subclasses may choose 4868 * to use these fields as special offsets.</p> 4869 * 4870 * @param dx horizontal distance scrolled in pixels 4871 * @param dy vertical distance scrolled in pixels 4872 */ 4873 public void onScrolled(@Px int dx, @Px int dy) { 4874 // Do nothing 4875 } 4876 4877 void dispatchOnScrolled(int hresult, int vresult) { 4878 mDispatchScrollCounter++; 4879 // Pass the current scrollX/scrollY values; no actual change in these properties occurred 4880 // but some general-purpose code may choose to respond to changes this way. 4881 final int scrollX = getScrollX(); 4882 final int scrollY = getScrollY(); 4883 onScrollChanged(scrollX, scrollY, scrollX, scrollY); 4884 4885 // Pass the real deltas to onScrolled, the RecyclerView-specific method. 4886 onScrolled(hresult, vresult); 4887 4888 // Invoke listeners last. Subclassed view methods always handle the event first. 4889 // All internal state is consistent by the time listeners are invoked. 4890 if (mScrollListener != null) { 4891 mScrollListener.onScrolled(this, hresult, vresult); 4892 } 4893 if (mScrollListeners != null) { 4894 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 4895 mScrollListeners.get(i).onScrolled(this, hresult, vresult); 4896 } 4897 } 4898 mDispatchScrollCounter--; 4899 } 4900 4901 /** 4902 * Called when the scroll state of this RecyclerView changes. Subclasses should use this 4903 * method to respond to state changes instead of an explicit listener. 4904 * 4905 * <p>This method will always be invoked before listeners, but after the LayoutManager 4906 * responds to the scroll state change.</p> 4907 * 4908 * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, 4909 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} 4910 */ 4911 public void onScrollStateChanged(int state) { 4912 // Do nothing 4913 } 4914 4915 void dispatchOnScrollStateChanged(int state) { 4916 // Let the LayoutManager go first; this allows it to bring any properties into 4917 // a consistent state before the RecyclerView subclass responds. 4918 if (mLayout != null) { 4919 mLayout.onScrollStateChanged(state); 4920 } 4921 4922 // Let the RecyclerView subclass handle this event next; any LayoutManager property 4923 // changes will be reflected by this time. 4924 onScrollStateChanged(state); 4925 4926 // Listeners go last. All other internal state is consistent by this point. 4927 if (mScrollListener != null) { 4928 mScrollListener.onScrollStateChanged(this, state); 4929 } 4930 if (mScrollListeners != null) { 4931 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 4932 mScrollListeners.get(i).onScrollStateChanged(this, state); 4933 } 4934 } 4935 } 4936 4937 /** 4938 * Returns whether there are pending adapter updates which are not yet applied to the layout. 4939 * <p> 4940 * If this method returns <code>true</code>, it means that what user is currently seeing may not 4941 * reflect them adapter contents (depending on what has changed). 4942 * You may use this information to defer or cancel some operations. 4943 * <p> 4944 * This method returns true if RecyclerView has not yet calculated the first layout after it is 4945 * attached to the Window or the Adapter has been replaced. 4946 * 4947 * @return True if there are some adapter updates which are not yet reflected to layout or false 4948 * if layout is up to date. 4949 */ 4950 public boolean hasPendingAdapterUpdates() { 4951 return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout 4952 || mAdapterHelper.hasPendingUpdates(); 4953 } 4954 4955 class ViewFlinger implements Runnable { 4956 private int mLastFlingX; 4957 private int mLastFlingY; 4958 private OverScroller mScroller; 4959 Interpolator mInterpolator = sQuinticInterpolator; 4960 4961 // When set to true, postOnAnimation callbacks are delayed until the run method completes 4962 private boolean mEatRunOnAnimationRequest = false; 4963 4964 // Tracks if postAnimationCallback should be re-attached when it is done 4965 private boolean mReSchedulePostAnimationCallback = false; 4966 4967 ViewFlinger() { 4968 mScroller = new OverScroller(getContext(), sQuinticInterpolator); 4969 } 4970 4971 @Override 4972 public void run() { 4973 if (mLayout == null) { 4974 stop(); 4975 return; // no layout, cannot scroll. 4976 } 4977 disableRunOnAnimationRequests(); 4978 consumePendingUpdateOperations(); 4979 // keep a local reference so that if it is changed during onAnimation method, it won't 4980 // cause unexpected behaviors 4981 final OverScroller scroller = mScroller; 4982 final SmoothScroller smoothScroller = mLayout.mSmoothScroller; 4983 if (scroller.computeScrollOffset()) { 4984 final int[] scrollConsumed = mScrollConsumed; 4985 final int x = scroller.getCurrX(); 4986 final int y = scroller.getCurrY(); 4987 int dx = x - mLastFlingX; 4988 int dy = y - mLastFlingY; 4989 int hresult = 0; 4990 int vresult = 0; 4991 mLastFlingX = x; 4992 mLastFlingY = y; 4993 int overscrollX = 0, overscrollY = 0; 4994 4995 if (dispatchNestedPreScroll(dx, dy, scrollConsumed, null, TYPE_NON_TOUCH)) { 4996 dx -= scrollConsumed[0]; 4997 dy -= scrollConsumed[1]; 4998 } 4999 5000 if (mAdapter != null) { 5001 scrollStep(dx, dy, mScrollStepConsumed); 5002 hresult = mScrollStepConsumed[0]; 5003 vresult = mScrollStepConsumed[1]; 5004 overscrollX = dx - hresult; 5005 overscrollY = dy - vresult; 5006 5007 if (smoothScroller != null && !smoothScroller.isPendingInitialRun() 5008 && smoothScroller.isRunning()) { 5009 final int adapterSize = mState.getItemCount(); 5010 if (adapterSize == 0) { 5011 smoothScroller.stop(); 5012 } else if (smoothScroller.getTargetPosition() >= adapterSize) { 5013 smoothScroller.setTargetPosition(adapterSize - 1); 5014 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 5015 } else { 5016 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); 5017 } 5018 } 5019 } 5020 if (!mItemDecorations.isEmpty()) { 5021 invalidate(); 5022 } 5023 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 5024 considerReleasingGlowsOnScroll(dx, dy); 5025 } 5026 5027 if (!dispatchNestedScroll(hresult, vresult, overscrollX, overscrollY, null, 5028 TYPE_NON_TOUCH) 5029 && (overscrollX != 0 || overscrollY != 0)) { 5030 final int vel = (int) scroller.getCurrVelocity(); 5031 5032 int velX = 0; 5033 if (overscrollX != x) { 5034 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; 5035 } 5036 5037 int velY = 0; 5038 if (overscrollY != y) { 5039 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; 5040 } 5041 5042 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 5043 absorbGlows(velX, velY); 5044 } 5045 if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) 5046 && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { 5047 scroller.abortAnimation(); 5048 } 5049 } 5050 if (hresult != 0 || vresult != 0) { 5051 dispatchOnScrolled(hresult, vresult); 5052 } 5053 5054 if (!awakenScrollBars()) { 5055 invalidate(); 5056 } 5057 5058 final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically() 5059 && vresult == dy; 5060 final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally() 5061 && hresult == dx; 5062 final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal 5063 || fullyConsumedVertical; 5064 5065 if (scroller.isFinished() || (!fullyConsumedAny 5066 && !hasNestedScrollingParent(TYPE_NON_TOUCH))) { 5067 // setting state to idle will stop this. 5068 setScrollState(SCROLL_STATE_IDLE); 5069 if (ALLOW_THREAD_GAP_WORK) { 5070 mPrefetchRegistry.clearPrefetchPositions(); 5071 } 5072 stopNestedScroll(TYPE_NON_TOUCH); 5073 } else { 5074 postOnAnimation(); 5075 if (mGapWorker != null) { 5076 mGapWorker.postFromTraversal(RecyclerView.this, dx, dy); 5077 } 5078 } 5079 } 5080 // call this after the onAnimation is complete not to have inconsistent callbacks etc. 5081 if (smoothScroller != null) { 5082 if (smoothScroller.isPendingInitialRun()) { 5083 smoothScroller.onAnimation(0, 0); 5084 } 5085 if (!mReSchedulePostAnimationCallback) { 5086 smoothScroller.stop(); //stop if it does not trigger any scroll 5087 } 5088 } 5089 enableRunOnAnimationRequests(); 5090 } 5091 5092 private void disableRunOnAnimationRequests() { 5093 mReSchedulePostAnimationCallback = false; 5094 mEatRunOnAnimationRequest = true; 5095 } 5096 5097 private void enableRunOnAnimationRequests() { 5098 mEatRunOnAnimationRequest = false; 5099 if (mReSchedulePostAnimationCallback) { 5100 postOnAnimation(); 5101 } 5102 } 5103 5104 void postOnAnimation() { 5105 if (mEatRunOnAnimationRequest) { 5106 mReSchedulePostAnimationCallback = true; 5107 } else { 5108 removeCallbacks(this); 5109 ViewCompat.postOnAnimation(RecyclerView.this, this); 5110 } 5111 } 5112 5113 public void fling(int velocityX, int velocityY) { 5114 setScrollState(SCROLL_STATE_SETTLING); 5115 mLastFlingX = mLastFlingY = 0; 5116 mScroller.fling(0, 0, velocityX, velocityY, 5117 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 5118 postOnAnimation(); 5119 } 5120 5121 public void smoothScrollBy(int dx, int dy) { 5122 smoothScrollBy(dx, dy, 0, 0); 5123 } 5124 5125 public void smoothScrollBy(int dx, int dy, int vx, int vy) { 5126 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); 5127 } 5128 5129 private float distanceInfluenceForSnapDuration(float f) { 5130 f -= 0.5f; // center the values about 0. 5131 f *= 0.3f * (float) Math.PI / 2.0f; 5132 return (float) Math.sin(f); 5133 } 5134 5135 private int computeScrollDuration(int dx, int dy, int vx, int vy) { 5136 final int absDx = Math.abs(dx); 5137 final int absDy = Math.abs(dy); 5138 final boolean horizontal = absDx > absDy; 5139 final int velocity = (int) Math.sqrt(vx * vx + vy * vy); 5140 final int delta = (int) Math.sqrt(dx * dx + dy * dy); 5141 final int containerSize = horizontal ? getWidth() : getHeight(); 5142 final int halfContainerSize = containerSize / 2; 5143 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); 5144 final float distance = halfContainerSize + halfContainerSize 5145 * distanceInfluenceForSnapDuration(distanceRatio); 5146 5147 final int duration; 5148 if (velocity > 0) { 5149 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 5150 } else { 5151 float absDelta = (float) (horizontal ? absDx : absDy); 5152 duration = (int) (((absDelta / containerSize) + 1) * 300); 5153 } 5154 return Math.min(duration, MAX_SCROLL_DURATION); 5155 } 5156 5157 public void smoothScrollBy(int dx, int dy, int duration) { 5158 smoothScrollBy(dx, dy, duration, sQuinticInterpolator); 5159 } 5160 5161 public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { 5162 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0), 5163 interpolator == null ? sQuinticInterpolator : interpolator); 5164 } 5165 5166 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { 5167 if (mInterpolator != interpolator) { 5168 mInterpolator = interpolator; 5169 mScroller = new OverScroller(getContext(), interpolator); 5170 } 5171 setScrollState(SCROLL_STATE_SETTLING); 5172 mLastFlingX = mLastFlingY = 0; 5173 mScroller.startScroll(0, 0, dx, dy, duration); 5174 if (Build.VERSION.SDK_INT < 23) { 5175 // b/64931938 before API 23, startScroll() does not reset getCurX()/getCurY() 5176 // to start values, which causes fillRemainingScrollValues() put in obsolete values 5177 // for LayoutManager.onLayoutChildren(). 5178 mScroller.computeScrollOffset(); 5179 } 5180 postOnAnimation(); 5181 } 5182 5183 public void stop() { 5184 removeCallbacks(this); 5185 mScroller.abortAnimation(); 5186 } 5187 5188 } 5189 5190 void repositionShadowingViews() { 5191 // Fix up shadow views used by change animations 5192 int count = mChildHelper.getChildCount(); 5193 for (int i = 0; i < count; i++) { 5194 View view = mChildHelper.getChildAt(i); 5195 ViewHolder holder = getChildViewHolder(view); 5196 if (holder != null && holder.mShadowingHolder != null) { 5197 View shadowingView = holder.mShadowingHolder.itemView; 5198 int left = view.getLeft(); 5199 int top = view.getTop(); 5200 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { 5201 shadowingView.layout(left, top, 5202 left + shadowingView.getWidth(), 5203 top + shadowingView.getHeight()); 5204 } 5205 } 5206 } 5207 } 5208 5209 private class RecyclerViewDataObserver extends AdapterDataObserver { 5210 RecyclerViewDataObserver() { 5211 } 5212 5213 @Override 5214 public void onChanged() { 5215 assertNotInLayoutOrScroll(null); 5216 mState.mStructureChanged = true; 5217 5218 processDataSetCompletelyChanged(true); 5219 if (!mAdapterHelper.hasPendingUpdates()) { 5220 requestLayout(); 5221 } 5222 } 5223 5224 @Override 5225 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 5226 assertNotInLayoutOrScroll(null); 5227 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { 5228 triggerUpdateProcessor(); 5229 } 5230 } 5231 5232 @Override 5233 public void onItemRangeInserted(int positionStart, int itemCount) { 5234 assertNotInLayoutOrScroll(null); 5235 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 5236 triggerUpdateProcessor(); 5237 } 5238 } 5239 5240 @Override 5241 public void onItemRangeRemoved(int positionStart, int itemCount) { 5242 assertNotInLayoutOrScroll(null); 5243 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 5244 triggerUpdateProcessor(); 5245 } 5246 } 5247 5248 @Override 5249 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 5250 assertNotInLayoutOrScroll(null); 5251 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 5252 triggerUpdateProcessor(); 5253 } 5254 } 5255 5256 void triggerUpdateProcessor() { 5257 if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { 5258 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); 5259 } else { 5260 mAdapterUpdateDuringMeasure = true; 5261 requestLayout(); 5262 } 5263 } 5264 } 5265 5266 /** 5267 * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews. 5268 * 5269 * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory) 5270 */ 5271 public static class EdgeEffectFactory { 5272 5273 @Retention(RetentionPolicy.SOURCE) 5274 @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM}) 5275 public @interface EdgeDirection {} 5276 5277 /** 5278 * Direction constant for the left edge 5279 */ 5280 public static final int DIRECTION_LEFT = 0; 5281 5282 /** 5283 * Direction constant for the top edge 5284 */ 5285 public static final int DIRECTION_TOP = 1; 5286 5287 /** 5288 * Direction constant for the right edge 5289 */ 5290 public static final int DIRECTION_RIGHT = 2; 5291 5292 /** 5293 * Direction constant for the bottom edge 5294 */ 5295 public static final int DIRECTION_BOTTOM = 3; 5296 5297 /** 5298 * Create a new EdgeEffect for the provided direction. 5299 */ 5300 protected @NonNull EdgeEffect createEdgeEffect(@NonNull RecyclerView view, 5301 @EdgeDirection int direction) { 5302 return new EdgeEffect(view.getContext()); 5303 } 5304 } 5305 5306 /** 5307 * RecycledViewPool lets you share Views between multiple RecyclerViews. 5308 * <p> 5309 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool 5310 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. 5311 * <p> 5312 * RecyclerView automatically creates a pool for itself if you don't provide one. 5313 */ 5314 public static class RecycledViewPool { 5315 private static final int DEFAULT_MAX_SCRAP = 5; 5316 5317 /** 5318 * Tracks both pooled holders, as well as create/bind timing metadata for the given type. 5319 * 5320 * Note that this tracks running averages of create/bind time across all RecyclerViews 5321 * (and, indirectly, Adapters) that use this pool. 5322 * 5323 * 1) This enables us to track average create and bind times across multiple adapters. Even 5324 * though create (and especially bind) may behave differently for different Adapter 5325 * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. 5326 * 5327 * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return 5328 * false for all other views of its type for the same deadline. This prevents items 5329 * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. 5330 */ 5331 static class ScrapData { 5332 final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); 5333 int mMaxScrap = DEFAULT_MAX_SCRAP; 5334 long mCreateRunningAverageNs = 0; 5335 long mBindRunningAverageNs = 0; 5336 } 5337 SparseArray<ScrapData> mScrap = new SparseArray<>(); 5338 5339 private int mAttachCount = 0; 5340 5341 /** 5342 * Discard all ViewHolders. 5343 */ 5344 public void clear() { 5345 for (int i = 0; i < mScrap.size(); i++) { 5346 ScrapData data = mScrap.valueAt(i); 5347 data.mScrapHeap.clear(); 5348 } 5349 } 5350 5351 /** 5352 * Sets the maximum number of ViewHolders to hold in the pool before discarding. 5353 * 5354 * @param viewType ViewHolder Type 5355 * @param max Maximum number 5356 */ 5357 public void setMaxRecycledViews(int viewType, int max) { 5358 ScrapData scrapData = getScrapDataForType(viewType); 5359 scrapData.mMaxScrap = max; 5360 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 5361 while (scrapHeap.size() > max) { 5362 scrapHeap.remove(scrapHeap.size() - 1); 5363 } 5364 } 5365 5366 /** 5367 * Returns the current number of Views held by the RecycledViewPool of the given view type. 5368 */ 5369 public int getRecycledViewCount(int viewType) { 5370 return getScrapDataForType(viewType).mScrapHeap.size(); 5371 } 5372 5373 /** 5374 * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are 5375 * present. 5376 * 5377 * @param viewType ViewHolder type. 5378 * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none 5379 * are present. 5380 */ 5381 @Nullable 5382 public ViewHolder getRecycledView(int viewType) { 5383 final ScrapData scrapData = mScrap.get(viewType); 5384 if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { 5385 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 5386 return scrapHeap.remove(scrapHeap.size() - 1); 5387 } 5388 return null; 5389 } 5390 5391 /** 5392 * Total number of ViewHolders held by the pool. 5393 * 5394 * @return Number of ViewHolders held by the pool. 5395 */ 5396 int size() { 5397 int count = 0; 5398 for (int i = 0; i < mScrap.size(); i++) { 5399 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap; 5400 if (viewHolders != null) { 5401 count += viewHolders.size(); 5402 } 5403 } 5404 return count; 5405 } 5406 5407 /** 5408 * Add a scrap ViewHolder to the pool. 5409 * <p> 5410 * If the pool is already full for that ViewHolder's type, it will be immediately discarded. 5411 * 5412 * @param scrap ViewHolder to be added to the pool. 5413 */ 5414 public void putRecycledView(ViewHolder scrap) { 5415 final int viewType = scrap.getItemViewType(); 5416 final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; 5417 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { 5418 return; 5419 } 5420 if (DEBUG && scrapHeap.contains(scrap)) { 5421 throw new IllegalArgumentException("this scrap item already exists"); 5422 } 5423 scrap.resetInternal(); 5424 scrapHeap.add(scrap); 5425 } 5426 5427 long runningAverage(long oldAverage, long newValue) { 5428 if (oldAverage == 0) { 5429 return newValue; 5430 } 5431 return (oldAverage / 4 * 3) + (newValue / 4); 5432 } 5433 5434 void factorInCreateTime(int viewType, long createTimeNs) { 5435 ScrapData scrapData = getScrapDataForType(viewType); 5436 scrapData.mCreateRunningAverageNs = runningAverage( 5437 scrapData.mCreateRunningAverageNs, createTimeNs); 5438 } 5439 5440 void factorInBindTime(int viewType, long bindTimeNs) { 5441 ScrapData scrapData = getScrapDataForType(viewType); 5442 scrapData.mBindRunningAverageNs = runningAverage( 5443 scrapData.mBindRunningAverageNs, bindTimeNs); 5444 } 5445 5446 boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { 5447 long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; 5448 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 5449 } 5450 5451 boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { 5452 long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; 5453 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 5454 } 5455 5456 void attach() { 5457 mAttachCount++; 5458 } 5459 5460 void detach() { 5461 mAttachCount--; 5462 } 5463 5464 5465 /** 5466 * Detaches the old adapter and attaches the new one. 5467 * <p> 5468 * RecycledViewPool will clear its cache if it has only one adapter attached and the new 5469 * adapter uses a different ViewHolder than the oldAdapter. 5470 * 5471 * @param oldAdapter The previous adapter instance. Will be detached. 5472 * @param newAdapter The new adapter instance. Will be attached. 5473 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same 5474 * ViewHolder and view types. 5475 */ 5476 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 5477 boolean compatibleWithPrevious) { 5478 if (oldAdapter != null) { 5479 detach(); 5480 } 5481 if (!compatibleWithPrevious && mAttachCount == 0) { 5482 clear(); 5483 } 5484 if (newAdapter != null) { 5485 attach(); 5486 } 5487 } 5488 5489 private ScrapData getScrapDataForType(int viewType) { 5490 ScrapData scrapData = mScrap.get(viewType); 5491 if (scrapData == null) { 5492 scrapData = new ScrapData(); 5493 mScrap.put(viewType, scrapData); 5494 } 5495 return scrapData; 5496 } 5497 } 5498 5499 /** 5500 * Utility method for finding an internal RecyclerView, if present 5501 */ 5502 @Nullable 5503 static RecyclerView findNestedRecyclerView(@NonNull View view) { 5504 if (!(view instanceof ViewGroup)) { 5505 return null; 5506 } 5507 if (view instanceof RecyclerView) { 5508 return (RecyclerView) view; 5509 } 5510 final ViewGroup parent = (ViewGroup) view; 5511 final int count = parent.getChildCount(); 5512 for (int i = 0; i < count; i++) { 5513 final View child = parent.getChildAt(i); 5514 final RecyclerView descendant = findNestedRecyclerView(child); 5515 if (descendant != null) { 5516 return descendant; 5517 } 5518 } 5519 return null; 5520 } 5521 5522 /** 5523 * Utility method for clearing holder's internal RecyclerView, if present 5524 */ 5525 static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { 5526 if (holder.mNestedRecyclerView != null) { 5527 View item = holder.mNestedRecyclerView.get(); 5528 while (item != null) { 5529 if (item == holder.itemView) { 5530 return; // match found, don't need to clear 5531 } 5532 5533 ViewParent parent = item.getParent(); 5534 if (parent instanceof View) { 5535 item = (View) parent; 5536 } else { 5537 item = null; 5538 } 5539 } 5540 holder.mNestedRecyclerView = null; // not nested 5541 } 5542 } 5543 5544 /** 5545 * Time base for deadline-aware work scheduling. Overridable for testing. 5546 * 5547 * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling 5548 * isn't relevant. 5549 */ 5550 long getNanoTime() { 5551 if (ALLOW_THREAD_GAP_WORK) { 5552 return System.nanoTime(); 5553 } else { 5554 return 0; 5555 } 5556 } 5557 5558 /** 5559 * A Recycler is responsible for managing scrapped or detached item views for reuse. 5560 * 5561 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 5562 * that has been marked for removal or reuse.</p> 5563 * 5564 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 5565 * an adapter's data set representing the data at a given position or item ID. 5566 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 5567 * If not, the view can be quickly reused by the LayoutManager with no further work. 5568 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 5569 * may be repositioned by a LayoutManager without remeasurement.</p> 5570 */ 5571 public final class Recycler { 5572 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); 5573 ArrayList<ViewHolder> mChangedScrap = null; 5574 5575 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 5576 5577 private final List<ViewHolder> 5578 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 5579 5580 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; 5581 int mViewCacheMax = DEFAULT_CACHE_SIZE; 5582 5583 RecycledViewPool mRecyclerPool; 5584 5585 private ViewCacheExtension mViewCacheExtension; 5586 5587 static final int DEFAULT_CACHE_SIZE = 2; 5588 5589 /** 5590 * Clear scrap views out of this recycler. Detached views contained within a 5591 * recycled view pool will remain. 5592 */ 5593 public void clear() { 5594 mAttachedScrap.clear(); 5595 recycleAndClearCachedViews(); 5596 } 5597 5598 /** 5599 * Set the maximum number of detached, valid views we should retain for later use. 5600 * 5601 * @param viewCount Number of views to keep before sending views to the shared pool 5602 */ 5603 public void setViewCacheSize(int viewCount) { 5604 mRequestedCacheMax = viewCount; 5605 updateViewCacheSize(); 5606 } 5607 5608 void updateViewCacheSize() { 5609 int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; 5610 mViewCacheMax = mRequestedCacheMax + extraCache; 5611 5612 // first, try the views that can be recycled 5613 for (int i = mCachedViews.size() - 1; 5614 i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { 5615 recycleCachedViewAt(i); 5616 } 5617 } 5618 5619 /** 5620 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. 5621 * 5622 * @return List of ViewHolders in the scrap list. 5623 */ 5624 @NonNull 5625 public List<ViewHolder> getScrapList() { 5626 return mUnmodifiableAttachedScrap; 5627 } 5628 5629 /** 5630 * Helper method for getViewForPosition. 5631 * <p> 5632 * Checks whether a given view holder can be used for the provided position. 5633 * 5634 * @param holder ViewHolder 5635 * @return true if ViewHolder matches the provided position, false otherwise 5636 */ 5637 boolean validateViewHolderForOffsetPosition(ViewHolder holder) { 5638 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore 5639 // if it is not removed, verify the type and id. 5640 if (holder.isRemoved()) { 5641 if (DEBUG && !mState.isPreLayout()) { 5642 throw new IllegalStateException("should not receive a removed view unless it" 5643 + " is pre layout" + exceptionLabel()); 5644 } 5645 return mState.isPreLayout(); 5646 } 5647 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { 5648 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " 5649 + "adapter position" + holder + exceptionLabel()); 5650 } 5651 if (!mState.isPreLayout()) { 5652 // don't check type if it is pre-layout. 5653 final int type = mAdapter.getItemViewType(holder.mPosition); 5654 if (type != holder.getItemViewType()) { 5655 return false; 5656 } 5657 } 5658 if (mAdapter.hasStableIds()) { 5659 return holder.getItemId() == mAdapter.getItemId(holder.mPosition); 5660 } 5661 return true; 5662 } 5663 5664 /** 5665 * Attempts to bind view, and account for relevant timing information. If 5666 * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. 5667 * 5668 * @param holder Holder to be bound. 5669 * @param offsetPosition Position of item to be bound. 5670 * @param position Pre-layout position of item to be bound. 5671 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 5672 * complete. If FOREVER_NS is passed, this method will not fail to 5673 * bind the holder. 5674 * @return 5675 */ 5676 private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, 5677 int position, long deadlineNs) { 5678 holder.mOwnerRecyclerView = RecyclerView.this; 5679 final int viewType = holder.getItemViewType(); 5680 long startBindNs = getNanoTime(); 5681 if (deadlineNs != FOREVER_NS 5682 && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { 5683 // abort - we have a deadline we can't meet 5684 return false; 5685 } 5686 mAdapter.bindViewHolder(holder, offsetPosition); 5687 long endBindNs = getNanoTime(); 5688 mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); 5689 attachAccessibilityDelegateOnBind(holder); 5690 if (mState.isPreLayout()) { 5691 holder.mPreLayoutPosition = position; 5692 } 5693 return true; 5694 } 5695 5696 /** 5697 * Binds the given View to the position. The View can be a View previously retrieved via 5698 * {@link #getViewForPosition(int)} or created by 5699 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. 5700 * <p> 5701 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} 5702 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who 5703 * wants to handle its own recycling logic. 5704 * <p> 5705 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so 5706 * you don't need to call this method unless you want to bind this View to another position. 5707 * 5708 * @param view The view to update. 5709 * @param position The position of the item to bind to this View. 5710 */ 5711 public void bindViewToPosition(@NonNull View view, int position) { 5712 ViewHolder holder = getChildViewHolderInt(view); 5713 if (holder == null) { 5714 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" 5715 + " pass arbitrary views to this method, they should be created by the " 5716 + "Adapter" + exceptionLabel()); 5717 } 5718 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5719 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 5720 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 5721 + "position " + position + "(offset:" + offsetPosition + ")." 5722 + "state:" + mState.getItemCount() + exceptionLabel()); 5723 } 5724 tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); 5725 5726 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 5727 final LayoutParams rvLayoutParams; 5728 if (lp == null) { 5729 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 5730 holder.itemView.setLayoutParams(rvLayoutParams); 5731 } else if (!checkLayoutParams(lp)) { 5732 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 5733 holder.itemView.setLayoutParams(rvLayoutParams); 5734 } else { 5735 rvLayoutParams = (LayoutParams) lp; 5736 } 5737 5738 rvLayoutParams.mInsetsDirty = true; 5739 rvLayoutParams.mViewHolder = holder; 5740 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; 5741 } 5742 5743 /** 5744 * RecyclerView provides artificial position range (item count) in pre-layout state and 5745 * automatically maps these positions to {@link Adapter} positions when 5746 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. 5747 * <p> 5748 * Usually, LayoutManager does not need to worry about this. However, in some cases, your 5749 * LayoutManager may need to call some custom component with item positions in which 5750 * case you need the actual adapter position instead of the pre layout position. You 5751 * can use this method to convert a pre-layout position to adapter (post layout) position. 5752 * <p> 5753 * Note that if the provided position belongs to a deleted ViewHolder, this method will 5754 * return -1. 5755 * <p> 5756 * Calling this method in post-layout state returns the same value back. 5757 * 5758 * @param position The pre-layout position to convert. Must be greater or equal to 0 and 5759 * less than {@link State#getItemCount()}. 5760 */ 5761 public int convertPreLayoutPositionToPostLayout(int position) { 5762 if (position < 0 || position >= mState.getItemCount()) { 5763 throw new IndexOutOfBoundsException("invalid position " + position + ". State " 5764 + "item count is " + mState.getItemCount() + exceptionLabel()); 5765 } 5766 if (!mState.isPreLayout()) { 5767 return position; 5768 } 5769 return mAdapterHelper.findPositionOffset(position); 5770 } 5771 5772 /** 5773 * Obtain a view initialized for the given position. 5774 * 5775 * This method should be used by {@link LayoutManager} implementations to obtain 5776 * views to represent data from an {@link Adapter}. 5777 * <p> 5778 * The Recycler may reuse a scrap or detached view from a shared pool if one is 5779 * available for the correct view type. If the adapter has not indicated that the 5780 * data at the given position has changed, the Recycler will attempt to hand back 5781 * a scrap view that was previously initialized for that data without rebinding. 5782 * 5783 * @param position Position to obtain a view for 5784 * @return A view representing the data at <code>position</code> from <code>adapter</code> 5785 */ 5786 @NonNull 5787 public View getViewForPosition(int position) { 5788 return getViewForPosition(position, false); 5789 } 5790 5791 View getViewForPosition(int position, boolean dryRun) { 5792 return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; 5793 } 5794 5795 /** 5796 * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, 5797 * cache, the RecycledViewPool, or creating it directly. 5798 * <p> 5799 * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return 5800 * rather than constructing or binding a ViewHolder if it doesn't think it has time. 5801 * If a ViewHolder must be constructed and not enough time remains, null is returned. If a 5802 * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is 5803 * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. 5804 * 5805 * @param position Position of ViewHolder to be returned. 5806 * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ 5807 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 5808 * complete. If FOREVER_NS is passed, this method will not fail to 5809 * create/bind the holder if needed. 5810 * 5811 * @return ViewHolder for requested position 5812 */ 5813 @Nullable 5814 ViewHolder tryGetViewHolderForPositionByDeadline(int position, 5815 boolean dryRun, long deadlineNs) { 5816 if (position < 0 || position >= mState.getItemCount()) { 5817 throw new IndexOutOfBoundsException("Invalid item position " + position 5818 + "(" + position + "). Item count:" + mState.getItemCount() 5819 + exceptionLabel()); 5820 } 5821 boolean fromScrapOrHiddenOrCache = false; 5822 ViewHolder holder = null; 5823 // 0) If there is a changed scrap, try to find from there 5824 if (mState.isPreLayout()) { 5825 holder = getChangedScrapViewForPosition(position); 5826 fromScrapOrHiddenOrCache = holder != null; 5827 } 5828 // 1) Find by position from scrap/hidden list/cache 5829 if (holder == null) { 5830 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); 5831 if (holder != null) { 5832 if (!validateViewHolderForOffsetPosition(holder)) { 5833 // recycle holder (and unscrap if relevant) since it can't be used 5834 if (!dryRun) { 5835 // we would like to recycle this but need to make sure it is not used by 5836 // animation logic etc. 5837 holder.addFlags(ViewHolder.FLAG_INVALID); 5838 if (holder.isScrap()) { 5839 removeDetachedView(holder.itemView, false); 5840 holder.unScrap(); 5841 } else if (holder.wasReturnedFromScrap()) { 5842 holder.clearReturnedFromScrapFlag(); 5843 } 5844 recycleViewHolderInternal(holder); 5845 } 5846 holder = null; 5847 } else { 5848 fromScrapOrHiddenOrCache = true; 5849 } 5850 } 5851 } 5852 if (holder == null) { 5853 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5854 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 5855 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 5856 + "position " + position + "(offset:" + offsetPosition + ")." 5857 + "state:" + mState.getItemCount() + exceptionLabel()); 5858 } 5859 5860 final int type = mAdapter.getItemViewType(offsetPosition); 5861 // 2) Find from scrap/cache via stable ids, if exists 5862 if (mAdapter.hasStableIds()) { 5863 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), 5864 type, dryRun); 5865 if (holder != null) { 5866 // update position 5867 holder.mPosition = offsetPosition; 5868 fromScrapOrHiddenOrCache = true; 5869 } 5870 } 5871 if (holder == null && mViewCacheExtension != null) { 5872 // We are NOT sending the offsetPosition because LayoutManager does not 5873 // know it. 5874 final View view = mViewCacheExtension 5875 .getViewForPositionAndType(this, position, type); 5876 if (view != null) { 5877 holder = getChildViewHolder(view); 5878 if (holder == null) { 5879 throw new IllegalArgumentException("getViewForPositionAndType returned" 5880 + " a view which does not have a ViewHolder" 5881 + exceptionLabel()); 5882 } else if (holder.shouldIgnore()) { 5883 throw new IllegalArgumentException("getViewForPositionAndType returned" 5884 + " a view that is ignored. You must call stopIgnoring before" 5885 + " returning this view." + exceptionLabel()); 5886 } 5887 } 5888 } 5889 if (holder == null) { // fallback to pool 5890 if (DEBUG) { 5891 Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" 5892 + position + ") fetching from shared pool"); 5893 } 5894 holder = getRecycledViewPool().getRecycledView(type); 5895 if (holder != null) { 5896 holder.resetInternal(); 5897 if (FORCE_INVALIDATE_DISPLAY_LIST) { 5898 invalidateDisplayListInt(holder); 5899 } 5900 } 5901 } 5902 if (holder == null) { 5903 long start = getNanoTime(); 5904 if (deadlineNs != FOREVER_NS 5905 && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { 5906 // abort - we have a deadline we can't meet 5907 return null; 5908 } 5909 holder = mAdapter.createViewHolder(RecyclerView.this, type); 5910 if (ALLOW_THREAD_GAP_WORK) { 5911 // only bother finding nested RV if prefetching 5912 RecyclerView innerView = findNestedRecyclerView(holder.itemView); 5913 if (innerView != null) { 5914 holder.mNestedRecyclerView = new WeakReference<>(innerView); 5915 } 5916 } 5917 5918 long end = getNanoTime(); 5919 mRecyclerPool.factorInCreateTime(type, end - start); 5920 if (DEBUG) { 5921 Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); 5922 } 5923 } 5924 } 5925 5926 // This is very ugly but the only place we can grab this information 5927 // before the View is rebound and returned to the LayoutManager for post layout ops. 5928 // We don't need this in pre-layout since the VH is not updated by the LM. 5929 if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder 5930 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { 5931 holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 5932 if (mState.mRunSimpleAnimations) { 5933 int changeFlags = ItemAnimator 5934 .buildAdapterChangeFlagsForAnimations(holder); 5935 changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 5936 final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, 5937 holder, changeFlags, holder.getUnmodifiedPayloads()); 5938 recordAnimationInfoIfBouncedHiddenView(holder, info); 5939 } 5940 } 5941 5942 boolean bound = false; 5943 if (mState.isPreLayout() && holder.isBound()) { 5944 // do not update unless we absolutely have to. 5945 holder.mPreLayoutPosition = position; 5946 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 5947 if (DEBUG && holder.isRemoved()) { 5948 throw new IllegalStateException("Removed holder should be bound and it should" 5949 + " come here only in pre-layout. Holder: " + holder 5950 + exceptionLabel()); 5951 } 5952 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 5953 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); 5954 } 5955 5956 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 5957 final LayoutParams rvLayoutParams; 5958 if (lp == null) { 5959 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 5960 holder.itemView.setLayoutParams(rvLayoutParams); 5961 } else if (!checkLayoutParams(lp)) { 5962 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 5963 holder.itemView.setLayoutParams(rvLayoutParams); 5964 } else { 5965 rvLayoutParams = (LayoutParams) lp; 5966 } 5967 rvLayoutParams.mViewHolder = holder; 5968 rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; 5969 return holder; 5970 } 5971 5972 private void attachAccessibilityDelegateOnBind(ViewHolder holder) { 5973 if (isAccessibilityEnabled()) { 5974 final View itemView = holder.itemView; 5975 if (ViewCompat.getImportantForAccessibility(itemView) 5976 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 5977 ViewCompat.setImportantForAccessibility(itemView, 5978 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 5979 } 5980 if (!ViewCompat.hasAccessibilityDelegate(itemView)) { 5981 holder.addFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE); 5982 ViewCompat.setAccessibilityDelegate(itemView, 5983 mAccessibilityDelegate.getItemDelegate()); 5984 } 5985 } 5986 } 5987 5988 private void invalidateDisplayListInt(ViewHolder holder) { 5989 if (holder.itemView instanceof ViewGroup) { 5990 invalidateDisplayListInt((ViewGroup) holder.itemView, false); 5991 } 5992 } 5993 5994 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { 5995 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 5996 final View view = viewGroup.getChildAt(i); 5997 if (view instanceof ViewGroup) { 5998 invalidateDisplayListInt((ViewGroup) view, true); 5999 } 6000 } 6001 if (!invalidateThis) { 6002 return; 6003 } 6004 // we need to force it to become invisible 6005 if (viewGroup.getVisibility() == View.INVISIBLE) { 6006 viewGroup.setVisibility(View.VISIBLE); 6007 viewGroup.setVisibility(View.INVISIBLE); 6008 } else { 6009 final int visibility = viewGroup.getVisibility(); 6010 viewGroup.setVisibility(View.INVISIBLE); 6011 viewGroup.setVisibility(visibility); 6012 } 6013 } 6014 6015 /** 6016 * Recycle a detached view. The specified view will be added to a pool of views 6017 * for later rebinding and reuse. 6018 * 6019 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the 6020 * View is scrapped, it will be removed from scrap list.</p> 6021 * 6022 * @param view Removed view for recycling 6023 * @see LayoutManager#removeAndRecycleView(View, Recycler) 6024 */ 6025 public void recycleView(@NonNull View view) { 6026 // This public recycle method tries to make view recycle-able since layout manager 6027 // intended to recycle this view (e.g. even if it is in scrap or change cache) 6028 ViewHolder holder = getChildViewHolderInt(view); 6029 if (holder.isTmpDetached()) { 6030 removeDetachedView(view, false); 6031 } 6032 if (holder.isScrap()) { 6033 holder.unScrap(); 6034 } else if (holder.wasReturnedFromScrap()) { 6035 holder.clearReturnedFromScrapFlag(); 6036 } 6037 recycleViewHolderInternal(holder); 6038 } 6039 6040 /** 6041 * Internally, use this method instead of {@link #recycleView(android.view.View)} to 6042 * catch potential bugs. 6043 * @param view 6044 */ 6045 void recycleViewInternal(View view) { 6046 recycleViewHolderInternal(getChildViewHolderInt(view)); 6047 } 6048 6049 void recycleAndClearCachedViews() { 6050 final int count = mCachedViews.size(); 6051 for (int i = count - 1; i >= 0; i--) { 6052 recycleCachedViewAt(i); 6053 } 6054 mCachedViews.clear(); 6055 if (ALLOW_THREAD_GAP_WORK) { 6056 mPrefetchRegistry.clearPrefetchPositions(); 6057 } 6058 } 6059 6060 /** 6061 * Recycles a cached view and removes the view from the list. Views are added to cache 6062 * if and only if they are recyclable, so this method does not check it again. 6063 * <p> 6064 * A small exception to this rule is when the view does not have an animator reference 6065 * but transient state is true (due to animations created outside ItemAnimator). In that 6066 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is 6067 * still recyclable since Adapter wants to do so. 6068 * 6069 * @param cachedViewIndex The index of the view in cached views list 6070 */ 6071 void recycleCachedViewAt(int cachedViewIndex) { 6072 if (DEBUG) { 6073 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); 6074 } 6075 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 6076 if (DEBUG) { 6077 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); 6078 } 6079 addViewHolderToRecycledViewPool(viewHolder, true); 6080 mCachedViews.remove(cachedViewIndex); 6081 } 6082 6083 /** 6084 * internal implementation checks if view is scrapped or attached and throws an exception 6085 * if so. 6086 * Public version un-scraps before calling recycle. 6087 */ 6088 void recycleViewHolderInternal(ViewHolder holder) { 6089 if (holder.isScrap() || holder.itemView.getParent() != null) { 6090 throw new IllegalArgumentException( 6091 "Scrapped or attached views may not be recycled. isScrap:" 6092 + holder.isScrap() + " isAttached:" 6093 + (holder.itemView.getParent() != null) + exceptionLabel()); 6094 } 6095 6096 if (holder.isTmpDetached()) { 6097 throw new IllegalArgumentException("Tmp detached view should be removed " 6098 + "from RecyclerView before it can be recycled: " + holder 6099 + exceptionLabel()); 6100 } 6101 6102 if (holder.shouldIgnore()) { 6103 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" 6104 + " should first call stopIgnoringView(view) before calling recycle." 6105 + exceptionLabel()); 6106 } 6107 //noinspection unchecked 6108 final boolean transientStatePreventsRecycling = holder 6109 .doesTransientStatePreventRecycling(); 6110 final boolean forceRecycle = mAdapter != null 6111 && transientStatePreventsRecycling 6112 && mAdapter.onFailedToRecycleView(holder); 6113 boolean cached = false; 6114 boolean recycled = false; 6115 if (DEBUG && mCachedViews.contains(holder)) { 6116 throw new IllegalArgumentException("cached view received recycle internal? " 6117 + holder + exceptionLabel()); 6118 } 6119 if (forceRecycle || holder.isRecyclable()) { 6120 if (mViewCacheMax > 0 6121 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 6122 | ViewHolder.FLAG_REMOVED 6123 | ViewHolder.FLAG_UPDATE 6124 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { 6125 // Retire oldest cached view 6126 int cachedViewSize = mCachedViews.size(); 6127 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { 6128 recycleCachedViewAt(0); 6129 cachedViewSize--; 6130 } 6131 6132 int targetCacheIndex = cachedViewSize; 6133 if (ALLOW_THREAD_GAP_WORK 6134 && cachedViewSize > 0 6135 && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { 6136 // when adding the view, skip past most recently prefetched views 6137 int cacheIndex = cachedViewSize - 1; 6138 while (cacheIndex >= 0) { 6139 int cachedPos = mCachedViews.get(cacheIndex).mPosition; 6140 if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { 6141 break; 6142 } 6143 cacheIndex--; 6144 } 6145 targetCacheIndex = cacheIndex + 1; 6146 } 6147 mCachedViews.add(targetCacheIndex, holder); 6148 cached = true; 6149 } 6150 if (!cached) { 6151 addViewHolderToRecycledViewPool(holder, true); 6152 recycled = true; 6153 } 6154 } else { 6155 // NOTE: A view can fail to be recycled when it is scrolled off while an animation 6156 // runs. In this case, the item is eventually recycled by 6157 // ItemAnimatorRestoreListener#onAnimationFinished. 6158 6159 // TODO: consider cancelling an animation when an item is removed scrollBy, 6160 // to return it to the pool faster 6161 if (DEBUG) { 6162 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " 6163 + "re-visit here. We are still removing it from animation lists" 6164 + exceptionLabel()); 6165 } 6166 } 6167 // even if the holder is not removed, we still call this method so that it is removed 6168 // from view holder lists. 6169 mViewInfoStore.removeViewHolder(holder); 6170 if (!cached && !recycled && transientStatePreventsRecycling) { 6171 holder.mOwnerRecyclerView = null; 6172 } 6173 } 6174 6175 /** 6176 * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. 6177 * 6178 * Pass false to dispatchRecycled for views that have not been bound. 6179 * 6180 * @param holder Holder to be added to the pool. 6181 * @param dispatchRecycled True to dispatch View recycled callbacks. 6182 */ 6183 void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { 6184 clearNestedRecyclerViewIfNotNested(holder); 6185 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) { 6186 holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE); 6187 ViewCompat.setAccessibilityDelegate(holder.itemView, null); 6188 } 6189 if (dispatchRecycled) { 6190 dispatchViewRecycled(holder); 6191 } 6192 holder.mOwnerRecyclerView = null; 6193 getRecycledViewPool().putRecycledView(holder); 6194 } 6195 6196 /** 6197 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 6198 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 6199 * internal bookkeeping. 6200 */ 6201 void quickRecycleScrapView(View view) { 6202 final ViewHolder holder = getChildViewHolderInt(view); 6203 holder.mScrapContainer = null; 6204 holder.mInChangeScrap = false; 6205 holder.clearReturnedFromScrapFlag(); 6206 recycleViewHolderInternal(holder); 6207 } 6208 6209 /** 6210 * Mark an attached view as scrap. 6211 * 6212 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 6213 * for rebinding and reuse. Requests for a view for a given position may return a 6214 * reused or rebound scrap view instance.</p> 6215 * 6216 * @param view View to scrap 6217 */ 6218 void scrapView(View view) { 6219 final ViewHolder holder = getChildViewHolderInt(view); 6220 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) 6221 || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { 6222 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { 6223 throw new IllegalArgumentException("Called scrap view with an invalid view." 6224 + " Invalid views cannot be reused from scrap, they should rebound from" 6225 + " recycler pool." + exceptionLabel()); 6226 } 6227 holder.setScrapContainer(this, false); 6228 mAttachedScrap.add(holder); 6229 } else { 6230 if (mChangedScrap == null) { 6231 mChangedScrap = new ArrayList<ViewHolder>(); 6232 } 6233 holder.setScrapContainer(this, true); 6234 mChangedScrap.add(holder); 6235 } 6236 } 6237 6238 /** 6239 * Remove a previously scrapped view from the pool of eligible scrap. 6240 * 6241 * <p>This view will no longer be eligible for reuse until re-scrapped or 6242 * until it is explicitly removed and recycled.</p> 6243 */ 6244 void unscrapView(ViewHolder holder) { 6245 if (holder.mInChangeScrap) { 6246 mChangedScrap.remove(holder); 6247 } else { 6248 mAttachedScrap.remove(holder); 6249 } 6250 holder.mScrapContainer = null; 6251 holder.mInChangeScrap = false; 6252 holder.clearReturnedFromScrapFlag(); 6253 } 6254 6255 int getScrapCount() { 6256 return mAttachedScrap.size(); 6257 } 6258 6259 View getScrapViewAt(int index) { 6260 return mAttachedScrap.get(index).itemView; 6261 } 6262 6263 void clearScrap() { 6264 mAttachedScrap.clear(); 6265 if (mChangedScrap != null) { 6266 mChangedScrap.clear(); 6267 } 6268 } 6269 6270 ViewHolder getChangedScrapViewForPosition(int position) { 6271 // If pre-layout, check the changed scrap for an exact match. 6272 final int changedScrapSize; 6273 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { 6274 return null; 6275 } 6276 // find by position 6277 for (int i = 0; i < changedScrapSize; i++) { 6278 final ViewHolder holder = mChangedScrap.get(i); 6279 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { 6280 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6281 return holder; 6282 } 6283 } 6284 // find by id 6285 if (mAdapter.hasStableIds()) { 6286 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 6287 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { 6288 final long id = mAdapter.getItemId(offsetPosition); 6289 for (int i = 0; i < changedScrapSize; i++) { 6290 final ViewHolder holder = mChangedScrap.get(i); 6291 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { 6292 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6293 return holder; 6294 } 6295 } 6296 } 6297 } 6298 return null; 6299 } 6300 6301 /** 6302 * Returns a view for the position either from attach scrap, hidden children, or cache. 6303 * 6304 * @param position Item position 6305 * @param dryRun Does a dry run, finds the ViewHolder but does not remove 6306 * @return a ViewHolder that can be re-used for this position. 6307 */ 6308 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { 6309 final int scrapCount = mAttachedScrap.size(); 6310 6311 // Try first for an exact, non-invalid match from scrap. 6312 for (int i = 0; i < scrapCount; i++) { 6313 final ViewHolder holder = mAttachedScrap.get(i); 6314 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position 6315 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { 6316 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6317 return holder; 6318 } 6319 } 6320 6321 if (!dryRun) { 6322 View view = mChildHelper.findHiddenNonRemovedView(position); 6323 if (view != null) { 6324 // This View is good to be used. We just need to unhide, detach and move to the 6325 // scrap list. 6326 final ViewHolder vh = getChildViewHolderInt(view); 6327 mChildHelper.unhide(view); 6328 int layoutIndex = mChildHelper.indexOfChild(view); 6329 if (layoutIndex == RecyclerView.NO_POSITION) { 6330 throw new IllegalStateException("layout index should not be -1 after " 6331 + "unhiding a view:" + vh + exceptionLabel()); 6332 } 6333 mChildHelper.detachViewFromParent(layoutIndex); 6334 scrapView(view); 6335 vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP 6336 | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 6337 return vh; 6338 } 6339 } 6340 6341 // Search in our first-level recycled view cache. 6342 final int cacheSize = mCachedViews.size(); 6343 for (int i = 0; i < cacheSize; i++) { 6344 final ViewHolder holder = mCachedViews.get(i); 6345 // invalid view holders may be in cache if adapter has stable ids as they can be 6346 // retrieved via getScrapOrCachedViewForId 6347 if (!holder.isInvalid() && holder.getLayoutPosition() == position) { 6348 if (!dryRun) { 6349 mCachedViews.remove(i); 6350 } 6351 if (DEBUG) { 6352 Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position 6353 + ") found match in cache: " + holder); 6354 } 6355 return holder; 6356 } 6357 } 6358 return null; 6359 } 6360 6361 ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { 6362 // Look in our attached views first 6363 final int count = mAttachedScrap.size(); 6364 for (int i = count - 1; i >= 0; i--) { 6365 final ViewHolder holder = mAttachedScrap.get(i); 6366 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { 6367 if (type == holder.getItemViewType()) { 6368 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 6369 if (holder.isRemoved()) { 6370 // this might be valid in two cases: 6371 // > item is removed but we are in pre-layout pass 6372 // >> do nothing. return as is. make sure we don't rebind 6373 // > item is removed then added to another position and we are in 6374 // post layout. 6375 // >> remove removed and invalid flags, add update flag to rebind 6376 // because item was invisible to us and we don't know what happened in 6377 // between. 6378 if (!mState.isPreLayout()) { 6379 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE 6380 | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); 6381 } 6382 } 6383 return holder; 6384 } else if (!dryRun) { 6385 // if we are running animations, it is actually better to keep it in scrap 6386 // but this would force layout manager to lay it out which would be bad. 6387 // Recycle this scrap. Type mismatch. 6388 mAttachedScrap.remove(i); 6389 removeDetachedView(holder.itemView, false); 6390 quickRecycleScrapView(holder.itemView); 6391 } 6392 } 6393 } 6394 6395 // Search the first-level cache 6396 final int cacheSize = mCachedViews.size(); 6397 for (int i = cacheSize - 1; i >= 0; i--) { 6398 final ViewHolder holder = mCachedViews.get(i); 6399 if (holder.getItemId() == id) { 6400 if (type == holder.getItemViewType()) { 6401 if (!dryRun) { 6402 mCachedViews.remove(i); 6403 } 6404 return holder; 6405 } else if (!dryRun) { 6406 recycleCachedViewAt(i); 6407 return null; 6408 } 6409 } 6410 } 6411 return null; 6412 } 6413 6414 void dispatchViewRecycled(@NonNull ViewHolder holder) { 6415 if (mRecyclerListener != null) { 6416 mRecyclerListener.onViewRecycled(holder); 6417 } 6418 if (mAdapter != null) { 6419 mAdapter.onViewRecycled(holder); 6420 } 6421 if (mState != null) { 6422 mViewInfoStore.removeViewHolder(holder); 6423 } 6424 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); 6425 } 6426 6427 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, 6428 boolean compatibleWithPrevious) { 6429 clear(); 6430 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); 6431 } 6432 6433 void offsetPositionRecordsForMove(int from, int to) { 6434 final int start, end, inBetweenOffset; 6435 if (from < to) { 6436 start = from; 6437 end = to; 6438 inBetweenOffset = -1; 6439 } else { 6440 start = to; 6441 end = from; 6442 inBetweenOffset = 1; 6443 } 6444 final int cachedCount = mCachedViews.size(); 6445 for (int i = 0; i < cachedCount; i++) { 6446 final ViewHolder holder = mCachedViews.get(i); 6447 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 6448 continue; 6449 } 6450 if (holder.mPosition == from) { 6451 holder.offsetPosition(to - from, false); 6452 } else { 6453 holder.offsetPosition(inBetweenOffset, false); 6454 } 6455 if (DEBUG) { 6456 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " 6457 + holder); 6458 } 6459 } 6460 } 6461 6462 void offsetPositionRecordsForInsert(int insertedAt, int count) { 6463 final int cachedCount = mCachedViews.size(); 6464 for (int i = 0; i < cachedCount; i++) { 6465 final ViewHolder holder = mCachedViews.get(i); 6466 if (holder != null && holder.mPosition >= insertedAt) { 6467 if (DEBUG) { 6468 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " 6469 + holder + " now at position " + (holder.mPosition + count)); 6470 } 6471 holder.offsetPosition(count, true); 6472 } 6473 } 6474 } 6475 6476 /** 6477 * @param removedFrom Remove start index 6478 * @param count Remove count 6479 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if 6480 * false, they'll be applied before the second layout pass 6481 */ 6482 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { 6483 final int removedEnd = removedFrom + count; 6484 final int cachedCount = mCachedViews.size(); 6485 for (int i = cachedCount - 1; i >= 0; i--) { 6486 final ViewHolder holder = mCachedViews.get(i); 6487 if (holder != null) { 6488 if (holder.mPosition >= removedEnd) { 6489 if (DEBUG) { 6490 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i 6491 + " holder " + holder + " now at position " 6492 + (holder.mPosition - count)); 6493 } 6494 holder.offsetPosition(-count, applyToPreLayout); 6495 } else if (holder.mPosition >= removedFrom) { 6496 // Item for this view was removed. Dump it from the cache. 6497 holder.addFlags(ViewHolder.FLAG_REMOVED); 6498 recycleCachedViewAt(i); 6499 } 6500 } 6501 } 6502 } 6503 6504 void setViewCacheExtension(ViewCacheExtension extension) { 6505 mViewCacheExtension = extension; 6506 } 6507 6508 void setRecycledViewPool(RecycledViewPool pool) { 6509 if (mRecyclerPool != null) { 6510 mRecyclerPool.detach(); 6511 } 6512 mRecyclerPool = pool; 6513 if (mRecyclerPool != null && getAdapter() != null) { 6514 mRecyclerPool.attach(); 6515 } 6516 } 6517 6518 RecycledViewPool getRecycledViewPool() { 6519 if (mRecyclerPool == null) { 6520 mRecyclerPool = new RecycledViewPool(); 6521 } 6522 return mRecyclerPool; 6523 } 6524 6525 void viewRangeUpdate(int positionStart, int itemCount) { 6526 final int positionEnd = positionStart + itemCount; 6527 final int cachedCount = mCachedViews.size(); 6528 for (int i = cachedCount - 1; i >= 0; i--) { 6529 final ViewHolder holder = mCachedViews.get(i); 6530 if (holder == null) { 6531 continue; 6532 } 6533 6534 final int pos = holder.mPosition; 6535 if (pos >= positionStart && pos < positionEnd) { 6536 holder.addFlags(ViewHolder.FLAG_UPDATE); 6537 recycleCachedViewAt(i); 6538 // cached views should not be flagged as changed because this will cause them 6539 // to animate when they are returned from cache. 6540 } 6541 } 6542 } 6543 6544 void markKnownViewsInvalid() { 6545 final int cachedCount = mCachedViews.size(); 6546 for (int i = 0; i < cachedCount; i++) { 6547 final ViewHolder holder = mCachedViews.get(i); 6548 if (holder != null) { 6549 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 6550 holder.addChangePayload(null); 6551 } 6552 } 6553 6554 if (mAdapter == null || !mAdapter.hasStableIds()) { 6555 // we cannot re-use cached views in this case. Recycle them all 6556 recycleAndClearCachedViews(); 6557 } 6558 } 6559 6560 void clearOldPositions() { 6561 final int cachedCount = mCachedViews.size(); 6562 for (int i = 0; i < cachedCount; i++) { 6563 final ViewHolder holder = mCachedViews.get(i); 6564 holder.clearOldPosition(); 6565 } 6566 final int scrapCount = mAttachedScrap.size(); 6567 for (int i = 0; i < scrapCount; i++) { 6568 mAttachedScrap.get(i).clearOldPosition(); 6569 } 6570 if (mChangedScrap != null) { 6571 final int changedScrapCount = mChangedScrap.size(); 6572 for (int i = 0; i < changedScrapCount; i++) { 6573 mChangedScrap.get(i).clearOldPosition(); 6574 } 6575 } 6576 } 6577 6578 void markItemDecorInsetsDirty() { 6579 final int cachedCount = mCachedViews.size(); 6580 for (int i = 0; i < cachedCount; i++) { 6581 final ViewHolder holder = mCachedViews.get(i); 6582 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); 6583 if (layoutParams != null) { 6584 layoutParams.mInsetsDirty = true; 6585 } 6586 } 6587 } 6588 } 6589 6590 /** 6591 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can 6592 * be controlled by the developer. 6593 * <p> 6594 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and 6595 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will 6596 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking 6597 * {@link RecycledViewPool}. 6598 * <p> 6599 * Note that, Recycler never sends Views to this method to be cached. It is developers 6600 * responsibility to decide whether they want to keep their Views in this custom cache or let 6601 * the default recycling policy handle it. 6602 */ 6603 public abstract static class ViewCacheExtension { 6604 6605 /** 6606 * Returns a View that can be binded to the given Adapter position. 6607 * <p> 6608 * This method should <b>not</b> create a new View. Instead, it is expected to return 6609 * an already created View that can be re-used for the given type and position. 6610 * If the View is marked as ignored, it should first call 6611 * {@link LayoutManager#stopIgnoringView(View)} before returning the View. 6612 * <p> 6613 * RecyclerView will re-bind the returned View to the position if necessary. 6614 * 6615 * @param recycler The Recycler that can be used to bind the View 6616 * @param position The adapter position 6617 * @param type The type of the View, defined by adapter 6618 * @return A View that is bound to the given position or NULL if there is no View to re-use 6619 * @see LayoutManager#ignoreView(View) 6620 */ 6621 @Nullable 6622 public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, 6623 int type); 6624 } 6625 6626 /** 6627 * Base class for an Adapter 6628 * 6629 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 6630 * within a {@link RecyclerView}.</p> 6631 * 6632 * @paramA class that extends ViewHolder that will be used by the adapter. 6633 */ 6634 public abstract static class Adapter<VH extends ViewHolder> { 6635 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 6636 private boolean mHasStableIds = false; 6637 6638 /** 6639 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent 6640 * an item. 6641 * <p> 6642 * This new ViewHolder should be constructed with a new View that can represent the items 6643 * of the given type. You can either create a new View manually or inflate it from an XML 6644 * layout file. 6645 * <p> 6646 * The new ViewHolder will be used to display items of the adapter using 6647 * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display 6648 * different items in the data set, it is a good idea to cache references to sub views of 6649 * the View to avoid unnecessary {@link View#findViewById(int)} calls. 6650 * 6651 * @param parent The ViewGroup into which the new View will be added after it is bound to 6652 * an adapter position. 6653 * @param viewType The view type of the new View. 6654 * 6655 * @return A new ViewHolder that holds a View of the given view type. 6656 * @see #getItemViewType(int) 6657 * @see #onBindViewHolder(ViewHolder, int) 6658 */ 6659 @NonNull 6660 public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); 6661 6662 /** 6663 * Called by RecyclerView to display the data at the specified position. This method should 6664 * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given 6665 * position. 6666 * <p> 6667 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 6668 * again if the position of the item changes in the data set unless the item itself is 6669 * invalidated or the new position cannot be determined. For this reason, you should only 6670 * use the <code>position</code> parameter while acquiring the related data item inside 6671 * this method and should not keep a copy of it. If you need the position of an item later 6672 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will 6673 * have the updated adapter position. 6674 * 6675 * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can 6676 * handle efficient partial bind. 6677 * 6678 * @param holder The ViewHolder which should be updated to represent the contents of the 6679 * item at the given position in the data set. 6680 * @param position The position of the item within the adapter's data set. 6681 */ 6682 public abstract void onBindViewHolder(@NonNull VH holder, int position); 6683 6684 /** 6685 * Called by RecyclerView to display the data at the specified position. This method 6686 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at 6687 * the given position. 6688 * <p> 6689 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 6690 * again if the position of the item changes in the data set unless the item itself is 6691 * invalidated or the new position cannot be determined. For this reason, you should only 6692 * use the <code>position</code> parameter while acquiring the related data item inside 6693 * this method and should not keep a copy of it. If you need the position of an item later 6694 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will 6695 * have the updated adapter position. 6696 * <p> 6697 * Partial bind vs full bind: 6698 * <p> 6699 * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or 6700 * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, 6701 * the ViewHolder is currently bound to old data and Adapter may run an efficient partial 6702 * update using the payload info. If the payload is empty, Adapter must run a full bind. 6703 * Adapter should not assume that the payload passed in notify methods will be received by 6704 * onBindViewHolder(). For example when the view is not attached to the screen, the 6705 * payload in notifyItemChange() will be simply dropped. 6706 * 6707 * @param holder The ViewHolder which should be updated to represent the contents of the 6708 * item at the given position in the data set. 6709 * @param position The position of the item within the adapter's data set. 6710 * @param payloads A non-null list of merged payloads. Can be empty list if requires full 6711 * update. 6712 */ 6713 public void onBindViewHolder(@NonNull VH holder, int position, 6714 @NonNull List<Object> payloads) { 6715 onBindViewHolder(holder, position); 6716 } 6717 6718 /** 6719 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new 6720 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. 6721 * 6722 * @see #onCreateViewHolder(ViewGroup, int) 6723 */ 6724 @NonNull 6725 public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { 6726 try { 6727 TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); 6728 final VH holder = onCreateViewHolder(parent, viewType); 6729 if (holder.itemView.getParent() != null) { 6730 throw new IllegalStateException("ViewHolder views must not be attached when" 6731 + " created. Ensure that you are not passing 'true' to the attachToRoot" 6732 + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); 6733 } 6734 holder.mItemViewType = viewType; 6735 return holder; 6736 } finally { 6737 TraceCompat.endSection(); 6738 } 6739 } 6740 6741 /** 6742 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the 6743 * {@link ViewHolder} contents with the item at the given position and also sets up some 6744 * private fields to be used by RecyclerView. 6745 * 6746 * @see #onBindViewHolder(ViewHolder, int) 6747 */ 6748 public final void bindViewHolder(@NonNull VH holder, int position) { 6749 holder.mPosition = position; 6750 if (hasStableIds()) { 6751 holder.mItemId = getItemId(position); 6752 } 6753 holder.setFlags(ViewHolder.FLAG_BOUND, 6754 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID 6755 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 6756 TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); 6757 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); 6758 holder.clearPayload(); 6759 final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); 6760 if (layoutParams instanceof RecyclerView.LayoutParams) { 6761 ((LayoutParams) layoutParams).mInsetsDirty = true; 6762 } 6763 TraceCompat.endSection(); 6764 } 6765 6766 /** 6767 * Return the view type of the item at <code>position</code> for the purposes 6768 * of view recycling. 6769 * 6770 * <p>The default implementation of this method returns 0, making the assumption of 6771 * a single view type for the adapter. Unlike ListView adapters, types need not 6772 * be contiguous. Consider using id resources to uniquely identify item view types. 6773 * 6774 * @param position position to query 6775 * @return integer value identifying the type of the view needed to represent the item at 6776 * <code>position</code>. Type codes need not be contiguous. 6777 */ 6778 public int getItemViewType(int position) { 6779 return 0; 6780 } 6781 6782 /** 6783 * Indicates whether each item in the data set can be represented with a unique identifier 6784 * of type {@link java.lang.Long}. 6785 * 6786 * @param hasStableIds Whether items in data set have unique identifiers or not. 6787 * @see #hasStableIds() 6788 * @see #getItemId(int) 6789 */ 6790 public void setHasStableIds(boolean hasStableIds) { 6791 if (hasObservers()) { 6792 throw new IllegalStateException("Cannot change whether this adapter has " 6793 + "stable IDs while the adapter has registered observers."); 6794 } 6795 mHasStableIds = hasStableIds; 6796 } 6797 6798 /** 6799 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 6800 * would return false this method should return {@link #NO_ID}. The default implementation 6801 * of this method returns {@link #NO_ID}. 6802 * 6803 * @param position Adapter position to query 6804 * @return the stable ID of the item at position 6805 */ 6806 public long getItemId(int position) { 6807 return NO_ID; 6808 } 6809 6810 /** 6811 * Returns the total number of items in the data set held by the adapter. 6812 * 6813 * @return The total number of items in this adapter. 6814 */ 6815 public abstract int getItemCount(); 6816 6817 /** 6818 * Returns true if this adapter publishes a unique <code>long</code> value that can 6819 * act as a key for the item at a given position in the data set. If that item is relocated 6820 * in the data set, the ID returned for that item should be the same. 6821 * 6822 * @return true if this adapter's items have stable IDs 6823 */ 6824 public final boolean hasStableIds() { 6825 return mHasStableIds; 6826 } 6827 6828 /** 6829 * Called when a view created by this adapter has been recycled. 6830 * 6831 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 6832 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 6833 * fallen out of visibility or a set of cached views represented by views still 6834 * attached to the parent RecyclerView. If an item view has large or expensive data 6835 * bound to it such as large bitmaps, this may be a good place to release those 6836 * resources.</p> 6837 * <p> 6838 * RecyclerView calls this method right before clearing ViewHolder's internal data and 6839 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 6840 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 6841 * its adapter position. 6842 * 6843 * @param holder The ViewHolder for the view being recycled 6844 */ 6845 public void onViewRecycled(@NonNull VH holder) { 6846 } 6847 6848 /** 6849 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled 6850 * due to its transient state. Upon receiving this callback, Adapter can clear the 6851 * animation(s) that effect the View's transient state and return <code>true</code> so that 6852 * the View can be recycled. Keep in mind that the View in question is already removed from 6853 * the RecyclerView. 6854 * <p> 6855 * In some cases, it is acceptable to recycle a View although it has transient state. Most 6856 * of the time, this is a case where the transient state will be cleared in 6857 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. 6858 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return 6859 * value of this method to decide whether the View should be recycled or not. 6860 * <p> 6861 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you 6862 * should never receive this callback because RecyclerView keeps those Views as children 6863 * until their animations are complete. This callback is useful when children of the item 6864 * views create animations which may not be easy to implement using an {@link ItemAnimator}. 6865 * <p> 6866 * You should <em>never</em> fix this issue by calling 6867 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called 6868 * <code>holder.itemView.setHasTransientState(true);</code>. Each 6869 * <code>View.setHasTransientState(true)</code> call must be matched by a 6870 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View 6871 * may become inconsistent. You should always prefer to end or cancel animations that are 6872 * triggering the transient state instead of handling it manually. 6873 * 6874 * @param holder The ViewHolder containing the View that could not be recycled due to its 6875 * transient state. 6876 * @return True if the View should be recycled, false otherwise. Note that if this method 6877 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of 6878 * the View and recycle it regardless. If this method returns <code>false</code>, 6879 * RecyclerView will check the View's transient state again before giving a final decision. 6880 * Default implementation returns false. 6881 */ 6882 public boolean onFailedToRecycleView(@NonNull VH holder) { 6883 return false; 6884 } 6885 6886 /** 6887 * Called when a view created by this adapter has been attached to a window. 6888 * 6889 * <p>This can be used as a reasonable signal that the view is about to be seen 6890 * by the user. If the adapter previously freed any resources in 6891 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 6892 * those resources should be restored here.</p> 6893 * 6894 * @param holder Holder of the view being attached 6895 */ 6896 public void onViewAttachedToWindow(@NonNull VH holder) { 6897 } 6898 6899 /** 6900 * Called when a view created by this adapter has been detached from its window. 6901 * 6902 * <p>Becoming detached from the window is not necessarily a permanent condition; 6903 * the consumer of an Adapter's views may choose to cache views offscreen while they 6904 * are not visible, attaching and detaching them as appropriate.</p> 6905 * 6906 * @param holder Holder of the view being detached 6907 */ 6908 public void onViewDetachedFromWindow(@NonNull VH holder) { 6909 } 6910 6911 /** 6912 * Returns true if one or more observers are attached to this adapter. 6913 * 6914 * @return true if this adapter has observers 6915 */ 6916 public final boolean hasObservers() { 6917 return mObservable.hasObservers(); 6918 } 6919 6920 /** 6921 * Register a new observer to listen for data changes. 6922 * 6923 * <p>The adapter may publish a variety of events describing specific changes. 6924 * Not all adapters may support all change types and some may fall back to a generic 6925 * {@link RecyclerView.AdapterDataObserver#onChanged() 6926 * "something changed"} event if more specific data is not available.</p> 6927 * 6928 * <p>Components registering observers with an adapter are responsible for 6929 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 6930 * unregistering} those observers when finished.</p> 6931 * 6932 * @param observer Observer to register 6933 * 6934 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 6935 */ 6936 public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { 6937 mObservable.registerObserver(observer); 6938 } 6939 6940 /** 6941 * Unregister an observer currently listening for data changes. 6942 * 6943 * <p>The unregistered observer will no longer receive events about changes 6944 * to the adapter.</p> 6945 * 6946 * @param observer Observer to unregister 6947 * 6948 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) 6949 */ 6950 public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { 6951 mObservable.unregisterObserver(observer); 6952 } 6953 6954 /** 6955 * Called by RecyclerView when it starts observing this Adapter. 6956 * <p> 6957 * Keep in mind that same adapter may be observed by multiple RecyclerViews. 6958 * 6959 * @param recyclerView The RecyclerView instance which started observing this adapter. 6960 * @see #onDetachedFromRecyclerView(RecyclerView) 6961 */ 6962 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 6963 } 6964 6965 /** 6966 * Called by RecyclerView when it stops observing this Adapter. 6967 * 6968 * @param recyclerView The RecyclerView instance which stopped observing this adapter. 6969 * @see #onAttachedToRecyclerView(RecyclerView) 6970 */ 6971 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 6972 } 6973 6974 /** 6975 * Notify any registered observers that the data set has changed. 6976 * 6977 * <p>There are two different classes of data change events, item changes and structural 6978 * changes. Item changes are when a single item has its data updated but no positional 6979 * changes have occurred. Structural changes are when items are inserted, removed or moved 6980 * within the data set.</p> 6981 * 6982 * <p>This event does not specify what about the data set has changed, forcing 6983 * any observers to assume that all existing items and structure may no longer be valid. 6984 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 6985 * 6986 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 6987 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 6988 * this method is used. This can help for the purposes of animation and visual 6989 * object persistence but individual item views will still need to be rebound 6990 * and relaid out.</p> 6991 * 6992 * <p>If you are writing an adapter it will always be more efficient to use the more 6993 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 6994 * as a last resort.</p> 6995 * 6996 * @see #notifyItemChanged(int) 6997 * @see #notifyItemInserted(int) 6998 * @see #notifyItemRemoved(int) 6999 * @see #notifyItemRangeChanged(int, int) 7000 * @see #notifyItemRangeInserted(int, int) 7001 * @see #notifyItemRangeRemoved(int, int) 7002 */ 7003 public final void notifyDataSetChanged() { 7004 mObservable.notifyChanged(); 7005 } 7006 7007 /** 7008 * Notify any registered observers that the item at <code>position</code> has changed. 7009 * Equivalent to calling <code>notifyItemChanged(position, null);</code>. 7010 * 7011 * <p>This is an item change event, not a structural change event. It indicates that any 7012 * reflection of the data at <code>position</code> is out of date and should be updated. 7013 * The item at <code>position</code> retains the same identity.</p> 7014 * 7015 * @param position Position of the item that has changed 7016 * 7017 * @see #notifyItemRangeChanged(int, int) 7018 */ 7019 public final void notifyItemChanged(int position) { 7020 mObservable.notifyItemRangeChanged(position, 1); 7021 } 7022 7023 /** 7024 * Notify any registered observers that the item at <code>position</code> has changed with 7025 * an optional payload object. 7026 * 7027 * <p>This is an item change event, not a structural change event. It indicates that any 7028 * reflection of the data at <code>position</code> is out of date and should be updated. 7029 * The item at <code>position</code> retains the same identity. 7030 * </p> 7031 * 7032 * <p> 7033 * Client can optionally pass a payload for partial change. These payloads will be merged 7034 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 7035 * item is already represented by a ViewHolder and it will be rebound to the same 7036 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 7037 * payloads on that item and prevent future payload until 7038 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 7039 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 7040 * attached, the payload will be simply dropped. 7041 * 7042 * @param position Position of the item that has changed 7043 * @param payload Optional parameter, use null to identify a "full" update 7044 * 7045 * @see #notifyItemRangeChanged(int, int) 7046 */ 7047 public final void notifyItemChanged(int position, @Nullable Object payload) { 7048 mObservable.notifyItemRangeChanged(position, 1, payload); 7049 } 7050 7051 /** 7052 * Notify any registered observers that the <code>itemCount</code> items starting at 7053 * position <code>positionStart</code> have changed. 7054 * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>. 7055 * 7056 * <p>This is an item change event, not a structural change event. It indicates that 7057 * any reflection of the data in the given position range is out of date and should 7058 * be updated. The items in the given range retain the same identity.</p> 7059 * 7060 * @param positionStart Position of the first item that has changed 7061 * @param itemCount Number of items that have changed 7062 * 7063 * @see #notifyItemChanged(int) 7064 */ 7065 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 7066 mObservable.notifyItemRangeChanged(positionStart, itemCount); 7067 } 7068 7069 /** 7070 * Notify any registered observers that the <code>itemCount</code> items starting at 7071 * position <code>positionStart</code> have changed. An optional payload can be 7072 * passed to each changed item. 7073 * 7074 * <p>This is an item change event, not a structural change event. It indicates that any 7075 * reflection of the data in the given position range is out of date and should be updated. 7076 * The items in the given range retain the same identity. 7077 * </p> 7078 * 7079 * <p> 7080 * Client can optionally pass a payload for partial change. These payloads will be merged 7081 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 7082 * item is already represented by a ViewHolder and it will be rebound to the same 7083 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 7084 * payloads on that item and prevent future payload until 7085 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 7086 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 7087 * attached, the payload will be simply dropped. 7088 * 7089 * @param positionStart Position of the first item that has changed 7090 * @param itemCount Number of items that have changed 7091 * @param payload Optional parameter, use null to identify a "full" update 7092 * 7093 * @see #notifyItemChanged(int) 7094 */ 7095 public final void notifyItemRangeChanged(int positionStart, int itemCount, 7096 @Nullable Object payload) { 7097 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); 7098 } 7099 7100 /** 7101 * Notify any registered observers that the item reflected at <code>position</code> 7102 * has been newly inserted. The item previously at <code>position</code> is now at 7103 * position <code>position + 1</code>. 7104 * 7105 * <p>This is a structural change event. Representations of other existing items in the 7106 * data set are still considered up to date and will not be rebound, though their 7107 * positions may be altered.</p> 7108 * 7109 * @param position Position of the newly inserted item in the data set 7110 * 7111 * @see #notifyItemRangeInserted(int, int) 7112 */ 7113 public final void notifyItemInserted(int position) { 7114 mObservable.notifyItemRangeInserted(position, 1); 7115 } 7116 7117 /** 7118 * Notify any registered observers that the item reflected at <code>fromPosition</code> 7119 * has been moved to <code>toPosition</code>. 7120 * 7121 * <p>This is a structural change event. Representations of other existing items in the 7122 * data set are still considered up to date and will not be rebound, though their 7123 * positions may be altered.</p> 7124 * 7125 * @param fromPosition Previous position of the item. 7126 * @param toPosition New position of the item. 7127 */ 7128 public final void notifyItemMoved(int fromPosition, int toPosition) { 7129 mObservable.notifyItemMoved(fromPosition, toPosition); 7130 } 7131 7132 /** 7133 * Notify any registered observers that the currently reflected <code>itemCount</code> 7134 * items starting at <code>positionStart</code> have been newly inserted. The items 7135 * previously located at <code>positionStart</code> and beyond can now be found starting 7136 * at position <code>positionStart + itemCount</code>. 7137 * 7138 * <p>This is a structural change event. Representations of other existing items in the 7139 * data set are still considered up to date and will not be rebound, though their positions 7140 * may be altered.</p> 7141 * 7142 * @param positionStart Position of the first item that was inserted 7143 * @param itemCount Number of items inserted 7144 * 7145 * @see #notifyItemInserted(int) 7146 */ 7147 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 7148 mObservable.notifyItemRangeInserted(positionStart, itemCount); 7149 } 7150 7151 /** 7152 * Notify any registered observers that the item previously located at <code>position</code> 7153 * has been removed from the data set. The items previously located at and after 7154 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 7155 * 7156 * <p>This is a structural change event. Representations of other existing items in the 7157 * data set are still considered up to date and will not be rebound, though their positions 7158 * may be altered.</p> 7159 * 7160 * @param position Position of the item that has now been removed 7161 * 7162 * @see #notifyItemRangeRemoved(int, int) 7163 */ 7164 public final void notifyItemRemoved(int position) { 7165 mObservable.notifyItemRangeRemoved(position, 1); 7166 } 7167 7168 /** 7169 * Notify any registered observers that the <code>itemCount</code> items previously 7170 * located at <code>positionStart</code> have been removed from the data set. The items 7171 * previously located at and after <code>positionStart + itemCount</code> may now be found 7172 * at <code>oldPosition - itemCount</code>. 7173 * 7174 * <p>This is a structural change event. Representations of other existing items in the data 7175 * set are still considered up to date and will not be rebound, though their positions 7176 * may be altered.</p> 7177 * 7178 * @param positionStart Previous position of the first item that was removed 7179 * @param itemCount Number of items removed from the data set 7180 */ 7181 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 7182 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 7183 } 7184 } 7185 7186 void dispatchChildDetached(View child) { 7187 final ViewHolder viewHolder = getChildViewHolderInt(child); 7188 onChildDetachedFromWindow(child); 7189 if (mAdapter != null && viewHolder != null) { 7190 mAdapter.onViewDetachedFromWindow(viewHolder); 7191 } 7192 if (mOnChildAttachStateListeners != null) { 7193 final int cnt = mOnChildAttachStateListeners.size(); 7194 for (int i = cnt - 1; i >= 0; i--) { 7195 mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child); 7196 } 7197 } 7198 } 7199 7200 void dispatchChildAttached(View child) { 7201 final ViewHolder viewHolder = getChildViewHolderInt(child); 7202 onChildAttachedToWindow(child); 7203 if (mAdapter != null && viewHolder != null) { 7204 mAdapter.onViewAttachedToWindow(viewHolder); 7205 } 7206 if (mOnChildAttachStateListeners != null) { 7207 final int cnt = mOnChildAttachStateListeners.size(); 7208 for (int i = cnt - 1; i >= 0; i--) { 7209 mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); 7210 } 7211 } 7212 } 7213 7214 /** 7215 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 7216 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 7217 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 7218 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 7219 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 7220 * layout managers are provided for general use. 7221 * <p/> 7222 * If the LayoutManager specifies a default constructor or one with the signature 7223 * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will 7224 * instantiate and set the LayoutManager when being inflated. Most used properties can 7225 * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case 7226 * a LayoutManager specifies both constructors, the non-default constructor will take 7227 * precedence. 7228 * 7229 */ 7230 public abstract static class LayoutManager { 7231 ChildHelper mChildHelper; 7232 RecyclerView mRecyclerView; 7233 7234 /** 7235 * The callback used for retrieving information about a RecyclerView and its children in the 7236 * horizontal direction. 7237 */ 7238 private final ViewBoundsCheck.Callback mHorizontalBoundCheckCallback = 7239 new ViewBoundsCheck.Callback() { 7240 @Override 7241 public int getChildCount() { 7242 return LayoutManager.this.getChildCount(); 7243 } 7244 7245 @Override 7246 public View getParent() { 7247 return mRecyclerView; 7248 } 7249 7250 @Override 7251 public View getChildAt(int index) { 7252 return LayoutManager.this.getChildAt(index); 7253 } 7254 7255 @Override 7256 public int getParentStart() { 7257 return LayoutManager.this.getPaddingLeft(); 7258 } 7259 7260 @Override 7261 public int getParentEnd() { 7262 return LayoutManager.this.getWidth() - LayoutManager.this.getPaddingRight(); 7263 } 7264 7265 @Override 7266 public int getChildStart(View view) { 7267 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7268 view.getLayoutParams(); 7269 return LayoutManager.this.getDecoratedLeft(view) - params.leftMargin; 7270 } 7271 7272 @Override 7273 public int getChildEnd(View view) { 7274 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7275 view.getLayoutParams(); 7276 return LayoutManager.this.getDecoratedRight(view) + params.rightMargin; 7277 } 7278 }; 7279 7280 /** 7281 * The callback used for retrieving information about a RecyclerView and its children in the 7282 * vertical direction. 7283 */ 7284 private final ViewBoundsCheck.Callback mVerticalBoundCheckCallback = 7285 new ViewBoundsCheck.Callback() { 7286 @Override 7287 public int getChildCount() { 7288 return LayoutManager.this.getChildCount(); 7289 } 7290 7291 @Override 7292 public View getParent() { 7293 return mRecyclerView; 7294 } 7295 7296 @Override 7297 public View getChildAt(int index) { 7298 return LayoutManager.this.getChildAt(index); 7299 } 7300 7301 @Override 7302 public int getParentStart() { 7303 return LayoutManager.this.getPaddingTop(); 7304 } 7305 7306 @Override 7307 public int getParentEnd() { 7308 return LayoutManager.this.getHeight() 7309 - LayoutManager.this.getPaddingBottom(); 7310 } 7311 7312 @Override 7313 public int getChildStart(View view) { 7314 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7315 view.getLayoutParams(); 7316 return LayoutManager.this.getDecoratedTop(view) - params.topMargin; 7317 } 7318 7319 @Override 7320 public int getChildEnd(View view) { 7321 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 7322 view.getLayoutParams(); 7323 return LayoutManager.this.getDecoratedBottom(view) + params.bottomMargin; 7324 } 7325 }; 7326 7327 /** 7328 * Utility objects used to check the boundaries of children against their parent 7329 * RecyclerView. 7330 * @see #isViewPartiallyVisible(View, boolean, boolean), 7331 * {@link LinearLayoutManager#findOneVisibleChild(int, int, boolean, boolean)}, 7332 * and {@link LinearLayoutManager#findOnePartiallyOrCompletelyInvisibleChild(int, int)}. 7333 */ 7334 ViewBoundsCheck mHorizontalBoundCheck = new ViewBoundsCheck(mHorizontalBoundCheckCallback); 7335 ViewBoundsCheck mVerticalBoundCheck = new ViewBoundsCheck(mVerticalBoundCheckCallback); 7336 7337 @Nullable 7338 SmoothScroller mSmoothScroller; 7339 7340 boolean mRequestedSimpleAnimations = false; 7341 7342 boolean mIsAttachedToWindow = false; 7343 7344 /** 7345 * This field is only set via the deprecated {@link #setAutoMeasureEnabled(boolean)} and is 7346 * only accessed via {@link #isAutoMeasureEnabled()} for backwards compatability reasons. 7347 */ 7348 boolean mAutoMeasure = false; 7349 7350 /** 7351 * LayoutManager has its own more strict measurement cache to avoid re-measuring a child 7352 * if the space that will be given to it is already larger than what it has measured before. 7353 */ 7354 private boolean mMeasurementCacheEnabled = true; 7355 7356 private boolean mItemPrefetchEnabled = true; 7357 7358 /** 7359 * Written by {@link GapWorker} when prefetches occur to track largest number of view ever 7360 * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or 7361 * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. 7362 * 7363 * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, 7364 * will be reset upon layout to prevent initial prefetches (often large, since they're 7365 * proportional to expected child count) from expanding cache permanently. 7366 */ 7367 int mPrefetchMaxCountObserved; 7368 7369 /** 7370 * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. 7371 */ 7372 boolean mPrefetchMaxObservedInInitialPrefetch; 7373 7374 /** 7375 * These measure specs might be the measure specs that were passed into RecyclerView's 7376 * onMeasure method OR fake measure specs created by the RecyclerView. 7377 * For example, when a layout is run, RecyclerView always sets these specs to be 7378 * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. 7379 * <p> 7380 * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the 7381 * API level and sets the size to 0 pre-M to avoid any issue that might be caused by 7382 * corrupt values. Older platforms have no responsibility to provide a size if they set 7383 * mode to unspecified. 7384 */ 7385 private int mWidthMode, mHeightMode; 7386 private int mWidth, mHeight; 7387 7388 7389 /** 7390 * Interface for LayoutManagers to request items to be prefetched, based on position, with 7391 * specified distance from viewport, which indicates priority. 7392 * 7393 * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 7394 * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 7395 */ 7396 public interface LayoutPrefetchRegistry { 7397 /** 7398 * Requests an an item to be prefetched, based on position, with a specified distance, 7399 * indicating priority. 7400 * 7401 * @param layoutPosition Position of the item to prefetch. 7402 * @param pixelDistance Distance from the current viewport to the bounds of the item, 7403 * must be non-negative. 7404 */ 7405 void addPosition(int layoutPosition, int pixelDistance); 7406 } 7407 7408 void setRecyclerView(RecyclerView recyclerView) { 7409 if (recyclerView == null) { 7410 mRecyclerView = null; 7411 mChildHelper = null; 7412 mWidth = 0; 7413 mHeight = 0; 7414 } else { 7415 mRecyclerView = recyclerView; 7416 mChildHelper = recyclerView.mChildHelper; 7417 mWidth = recyclerView.getWidth(); 7418 mHeight = recyclerView.getHeight(); 7419 } 7420 mWidthMode = MeasureSpec.EXACTLY; 7421 mHeightMode = MeasureSpec.EXACTLY; 7422 } 7423 7424 void setMeasureSpecs(int wSpec, int hSpec) { 7425 mWidth = MeasureSpec.getSize(wSpec); 7426 mWidthMode = MeasureSpec.getMode(wSpec); 7427 if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 7428 mWidth = 0; 7429 } 7430 7431 mHeight = MeasureSpec.getSize(hSpec); 7432 mHeightMode = MeasureSpec.getMode(hSpec); 7433 if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 7434 mHeight = 0; 7435 } 7436 } 7437 7438 /** 7439 * Called after a layout is calculated during a measure pass when using auto-measure. 7440 * <p> 7441 * It simply traverses all children to calculate a bounding box then calls 7442 * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method 7443 * if they need to handle the bounding box differently. 7444 * <p> 7445 * For example, GridLayoutManager override that method to ensure that even if a column is 7446 * empty, the GridLayoutManager still measures wide enough to include it. 7447 * 7448 * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure 7449 * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure 7450 */ 7451 void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { 7452 final int count = getChildCount(); 7453 if (count == 0) { 7454 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 7455 return; 7456 } 7457 int minX = Integer.MAX_VALUE; 7458 int minY = Integer.MAX_VALUE; 7459 int maxX = Integer.MIN_VALUE; 7460 int maxY = Integer.MIN_VALUE; 7461 7462 for (int i = 0; i < count; i++) { 7463 View child = getChildAt(i); 7464 final Rect bounds = mRecyclerView.mTempRect; 7465 getDecoratedBoundsWithMargins(child, bounds); 7466 if (bounds.left < minX) { 7467 minX = bounds.left; 7468 } 7469 if (bounds.right > maxX) { 7470 maxX = bounds.right; 7471 } 7472 if (bounds.top < minY) { 7473 minY = bounds.top; 7474 } 7475 if (bounds.bottom > maxY) { 7476 maxY = bounds.bottom; 7477 } 7478 } 7479 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); 7480 setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); 7481 } 7482 7483 /** 7484 * Sets the measured dimensions from the given bounding box of the children and the 7485 * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is 7486 * only called if a LayoutManager returns <code>true</code> from 7487 * {@link #isAutoMeasureEnabled()} and it is called after the RecyclerView calls 7488 * {@link LayoutManager#onLayoutChildren(Recycler, State)} in the execution of 7489 * {@link RecyclerView#onMeasure(int, int)}. 7490 * <p> 7491 * This method must call {@link #setMeasuredDimension(int, int)}. 7492 * <p> 7493 * The default implementation adds the RecyclerView's padding to the given bounding box 7494 * then caps the value to be within the given measurement specs. 7495 * 7496 * @param childrenBounds The bounding box of all children 7497 * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. 7498 * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. 7499 * 7500 * @see #isAutoMeasureEnabled() 7501 * @see #setMeasuredDimension(int, int) 7502 */ 7503 public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { 7504 int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); 7505 int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); 7506 int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); 7507 int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); 7508 setMeasuredDimension(width, height); 7509 } 7510 7511 /** 7512 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView 7513 */ 7514 public void requestLayout() { 7515 if (mRecyclerView != null) { 7516 mRecyclerView.requestLayout(); 7517 } 7518 } 7519 7520 /** 7521 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 7522 * {@link IllegalStateException} if it <b>is not</b>. 7523 * 7524 * @param message The message for the exception. Can be null. 7525 * @see #assertNotInLayoutOrScroll(String) 7526 */ 7527 public void assertInLayoutOrScroll(String message) { 7528 if (mRecyclerView != null) { 7529 mRecyclerView.assertInLayoutOrScroll(message); 7530 } 7531 } 7532 7533 /** 7534 * Chooses a size from the given specs and parameters that is closest to the desired size 7535 * and also complies with the spec. 7536 * 7537 * @param spec The measureSpec 7538 * @param desired The preferred measurement 7539 * @param min The minimum value 7540 * 7541 * @return A size that fits to the given specs 7542 */ 7543 public static int chooseSize(int spec, int desired, int min) { 7544 final int mode = View.MeasureSpec.getMode(spec); 7545 final int size = View.MeasureSpec.getSize(spec); 7546 switch (mode) { 7547 case View.MeasureSpec.EXACTLY: 7548 return size; 7549 case View.MeasureSpec.AT_MOST: 7550 return Math.min(size, Math.max(desired, min)); 7551 case View.MeasureSpec.UNSPECIFIED: 7552 default: 7553 return Math.max(desired, min); 7554 } 7555 } 7556 7557 /** 7558 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 7559 * {@link IllegalStateException} if it <b>is</b>. 7560 * 7561 * @param message The message for the exception. Can be null. 7562 * @see #assertInLayoutOrScroll(String) 7563 */ 7564 public void assertNotInLayoutOrScroll(String message) { 7565 if (mRecyclerView != null) { 7566 mRecyclerView.assertNotInLayoutOrScroll(message); 7567 } 7568 } 7569 7570 /** 7571 * Defines whether the measuring pass of layout should use the AutoMeasure mechanism of 7572 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 7573 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7574 * 7575 * @param enabled <code>True</code> if layout measurement should be done by the 7576 * RecyclerView, <code>false</code> if it should be done by this 7577 * LayoutManager. 7578 * 7579 * @see #isAutoMeasureEnabled() 7580 * 7581 * @deprecated Implementors of LayoutManager should define whether or not it uses 7582 * AutoMeasure by overriding {@link #isAutoMeasureEnabled()}. 7583 */ 7584 @Deprecated 7585 public void setAutoMeasureEnabled(boolean enabled) { 7586 mAutoMeasure = enabled; 7587 } 7588 7589 /** 7590 * Returns whether the measuring pass of layout should use the AutoMeasure mechanism of 7591 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 7592 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7593 * <p> 7594 * This method returns false by default (it actually returns the value passed to the 7595 * deprecated {@link #setAutoMeasureEnabled(boolean)}) and should be overridden to return 7596 * true if a LayoutManager wants to be auto measured by the RecyclerView. 7597 * <p> 7598 * If this method is overridden to return true, 7599 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} should not be overridden. 7600 * <p> 7601 * AutoMeasure is a RecyclerView mechanism that handles the measuring pass of layout in a 7602 * simple and contract satisfying way, including the wrapping of children laid out by 7603 * LayoutManager. Simply put, it handles wrapping children by calling 7604 * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a call to 7605 * {@link RecyclerView#onMeasure(int, int)}, and then calculating desired dimensions based 7606 * on children's dimensions and positions. It does this while supporting all existing 7607 * animation capabilities of the RecyclerView. 7608 * <p> 7609 * More specifically: 7610 * <ol> 7611 * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided measure 7612 * specs both have a mode of {@link View.MeasureSpec#EXACTLY}, RecyclerView will set its 7613 * measured dimensions accordingly and return, allowing layout to continue as normal 7614 * (Actually, RecyclerView will call 7615 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} for backwards compatibility 7616 * reasons but it should not be overridden if AutoMeasure is being used).</li> 7617 * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the 7618 * layout process. It will first process all pending Adapter updates and 7619 * then decide whether to run a predictive layout. If it decides to do so, it will first 7620 * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to 7621 * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still 7622 * return the width and height of the RecyclerView as of the last layout calculation. 7623 * <p> 7624 * After handling the predictive case, RecyclerView will call 7625 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 7626 * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can 7627 * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, 7628 * {@link #getWidth()} and {@link #getWidthMode()}.</li> 7629 * <li>After the layout calculation, RecyclerView sets the measured width & height by 7630 * calculating the bounding box for the children (+ RecyclerView's padding). The 7631 * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose 7632 * different values. For instance, GridLayoutManager overrides this value to handle the case 7633 * where if it is vertical and has 3 columns but only 2 items, it should still measure its 7634 * width to fit 3 items, not 2.</li> 7635 * <li>Any following calls to {@link RecyclerView#onMeasure(int, int)} will run 7636 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 7637 * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will 7638 * take care of which views are actually added / removed / moved / changed for animations so 7639 * that the LayoutManager should not worry about them and handle each 7640 * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.</li> 7641 * <li>When measure is complete and RecyclerView's 7642 * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks 7643 * whether it already did layout calculations during the measure pass and if so, it re-uses 7644 * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} 7645 * if the last measure spec was different from the final dimensions or adapter contents 7646 * have changed between the measure call and the layout call.</li> 7647 * <li>Finally, animations are calculated and run as usual.</li> 7648 * </ol> 7649 * 7650 * @return <code>True</code> if the measuring pass of layout should use the AutoMeasure 7651 * mechanism of {@link RecyclerView} or <code>False</code> if it should be done by the 7652 * LayoutManager's implementation of 7653 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 7654 * 7655 * @see #setMeasuredDimension(Rect, int, int) 7656 * @see #onMeasure(Recycler, State, int, int) 7657 */ 7658 public boolean isAutoMeasureEnabled() { 7659 return mAutoMeasure; 7660 } 7661 7662 /** 7663 * Returns whether this LayoutManager supports "predictive item animations". 7664 * <p> 7665 * "Predictive item animations" are automatically created animations that show 7666 * where items came from, and where they are going to, as items are added, removed, 7667 * or moved within a layout. 7668 * <p> 7669 * A LayoutManager wishing to support predictive item animations must override this 7670 * method to return true (the default implementation returns false) and must obey certain 7671 * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}. 7672 * <p> 7673 * Whether item animations actually occur in a RecyclerView is actually determined by both 7674 * the return value from this method and the 7675 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the 7676 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this 7677 * method returns false, then only "simple item animations" will be enabled in the 7678 * RecyclerView, in which views whose position are changing are simply faded in/out. If the 7679 * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive 7680 * item animations will be enabled in the RecyclerView. 7681 * 7682 * @return true if this LayoutManager supports predictive item animations, false otherwise. 7683 */ 7684 public boolean supportsPredictiveItemAnimations() { 7685 return false; 7686 } 7687 7688 /** 7689 * Sets whether the LayoutManager should be queried for views outside of 7690 * its viewport while the UI thread is idle between frames. 7691 * 7692 * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between 7693 * view system traversals on devices running API 21 or greater. Default value is true.</p> 7694 * 7695 * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame 7696 * to RenderThread and the starting up its next frame at the next VSync pulse. By 7697 * prefetching out of window views in this time period, delays from inflation and view 7698 * binding are much less likely to cause jank and stuttering during scrolls and flings.</p> 7699 * 7700 * <p>While prefetch is enabled, it will have the side effect of expanding the effective 7701 * size of the View cache to hold prefetched views.</p> 7702 * 7703 * @param enabled <code>True</code> if items should be prefetched in between traversals. 7704 * 7705 * @see #isItemPrefetchEnabled() 7706 */ 7707 public final void setItemPrefetchEnabled(boolean enabled) { 7708 if (enabled != mItemPrefetchEnabled) { 7709 mItemPrefetchEnabled = enabled; 7710 mPrefetchMaxCountObserved = 0; 7711 if (mRecyclerView != null) { 7712 mRecyclerView.mRecycler.updateViewCacheSize(); 7713 } 7714 } 7715 } 7716 7717 /** 7718 * Sets whether the LayoutManager should be queried for views outside of 7719 * its viewport while the UI thread is idle between frames. 7720 * 7721 * @see #setItemPrefetchEnabled(boolean) 7722 * 7723 * @return true if item prefetch is enabled, false otherwise 7724 */ 7725 public final boolean isItemPrefetchEnabled() { 7726 return mItemPrefetchEnabled; 7727 } 7728 7729 /** 7730 * Gather all positions from the LayoutManager to be prefetched, given specified momentum. 7731 * 7732 * <p>If item prefetch is enabled, this method is called in between traversals to gather 7733 * which positions the LayoutManager will soon need, given upcoming movement in subsequent 7734 * traversals.</p> 7735 * 7736 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 7737 * each item to be prepared, and these positions will have their ViewHolders created and 7738 * bound, if there is sufficient time available, in advance of being needed by a 7739 * scroll or layout.</p> 7740 * 7741 * @param dx X movement component. 7742 * @param dy Y movement component. 7743 * @param state State of RecyclerView 7744 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 7745 * 7746 * @see #isItemPrefetchEnabled() 7747 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 7748 */ 7749 public void collectAdjacentPrefetchPositions(int dx, int dy, State state, 7750 LayoutPrefetchRegistry layoutPrefetchRegistry) {} 7751 7752 /** 7753 * Gather all positions from the LayoutManager to be prefetched in preperation for its 7754 * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. 7755 * 7756 * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p> 7757 * 7758 * <p>If item prefetch is enabled for this LayoutManager, as well in another containing 7759 * LayoutManager, this method is called in between draw traversals to gather 7760 * which positions this LayoutManager will first need, once it appears on the screen.</p> 7761 * 7762 * <p>For example, if this LayoutManager represents a horizontally scrolling list within a 7763 * vertically scrolling LayoutManager, this method would be called when the horizontal list 7764 * is about to come onscreen.</p> 7765 * 7766 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 7767 * each item to be prepared, and these positions will have their ViewHolders created and 7768 * bound, if there is sufficient time available, in advance of being needed by a 7769 * scroll or layout.</p> 7770 * 7771 * @param adapterItemCount number of items in the associated adapter. 7772 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 7773 * 7774 * @see #isItemPrefetchEnabled() 7775 * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 7776 */ 7777 public void collectInitialPrefetchPositions(int adapterItemCount, 7778 LayoutPrefetchRegistry layoutPrefetchRegistry) {} 7779 7780 void dispatchAttachedToWindow(RecyclerView view) { 7781 mIsAttachedToWindow = true; 7782 onAttachedToWindow(view); 7783 } 7784 7785 void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { 7786 mIsAttachedToWindow = false; 7787 onDetachedFromWindow(view, recycler); 7788 } 7789 7790 /** 7791 * Returns whether LayoutManager is currently attached to a RecyclerView which is attached 7792 * to a window. 7793 * 7794 * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView 7795 * is attached to window. 7796 */ 7797 public boolean isAttachedToWindow() { 7798 return mIsAttachedToWindow; 7799 } 7800 7801 /** 7802 * Causes the Runnable to execute on the next animation time step. 7803 * The runnable will be run on the user interface thread. 7804 * <p> 7805 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 7806 * 7807 * @param action The Runnable that will be executed. 7808 * 7809 * @see #removeCallbacks 7810 */ 7811 public void postOnAnimation(Runnable action) { 7812 if (mRecyclerView != null) { 7813 ViewCompat.postOnAnimation(mRecyclerView, action); 7814 } 7815 } 7816 7817 /** 7818 * Removes the specified Runnable from the message queue. 7819 * <p> 7820 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 7821 * 7822 * @param action The Runnable to remove from the message handling queue 7823 * 7824 * @return true if RecyclerView could ask the Handler to remove the Runnable, 7825 * false otherwise. When the returned value is true, the Runnable 7826 * may or may not have been actually removed from the message queue 7827 * (for instance, if the Runnable was not in the queue already.) 7828 * 7829 * @see #postOnAnimation 7830 */ 7831 public boolean removeCallbacks(Runnable action) { 7832 if (mRecyclerView != null) { 7833 return mRecyclerView.removeCallbacks(action); 7834 } 7835 return false; 7836 } 7837 /** 7838 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 7839 * is attached to a window. 7840 * <p> 7841 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 7842 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 7843 * not requested on the RecyclerView while it was detached. 7844 * <p> 7845 * Subclass implementations should always call through to the superclass implementation. 7846 * 7847 * @param view The RecyclerView this LayoutManager is bound to 7848 * 7849 * @see #onDetachedFromWindow(RecyclerView, Recycler) 7850 */ 7851 @CallSuper 7852 public void onAttachedToWindow(RecyclerView view) { 7853 } 7854 7855 /** 7856 * @deprecated 7857 * override {@link #onDetachedFromWindow(RecyclerView, Recycler)} 7858 */ 7859 @Deprecated 7860 public void onDetachedFromWindow(RecyclerView view) { 7861 7862 } 7863 7864 /** 7865 * Called when this LayoutManager is detached from its parent RecyclerView or when 7866 * its parent RecyclerView is detached from its window. 7867 * <p> 7868 * LayoutManager should clear all of its View references as another LayoutManager might be 7869 * assigned to the RecyclerView. 7870 * <p> 7871 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 7872 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 7873 * not requested on the RecyclerView while it was detached. 7874 * <p> 7875 * If your LayoutManager has View references that it cleans in on-detach, it should also 7876 * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when 7877 * RecyclerView is re-attached. 7878 * <p> 7879 * Subclass implementations should always call through to the superclass implementation. 7880 * 7881 * @param view The RecyclerView this LayoutManager is bound to 7882 * @param recycler The recycler to use if you prefer to recycle your children instead of 7883 * keeping them around. 7884 * 7885 * @see #onAttachedToWindow(RecyclerView) 7886 */ 7887 @CallSuper 7888 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { 7889 onDetachedFromWindow(view); 7890 } 7891 7892 /** 7893 * Check if the RecyclerView is configured to clip child views to its padding. 7894 * 7895 * @return true if this RecyclerView clips children to its padding, false otherwise 7896 */ 7897 public boolean getClipToPadding() { 7898 return mRecyclerView != null && mRecyclerView.mClipToPadding; 7899 } 7900 7901 /** 7902 * Lay out all relevant child views from the given adapter. 7903 * 7904 * The LayoutManager is in charge of the behavior of item animations. By default, 7905 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple 7906 * item animations are enabled. This means that add/remove operations on the 7907 * adapter will result in animations to add new or appearing items, removed or 7908 * disappearing items, and moved items. If a LayoutManager returns false from 7909 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a 7910 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the 7911 * RecyclerView will have enough information to run those animations in a simple 7912 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will 7913 * simply fade views in and out, whether they are actually added/removed or whether 7914 * they are moved on or off the screen due to other add/remove operations. 7915 * 7916 * <p>A LayoutManager wanting a better item animation experience, where items can be 7917 * animated onto and off of the screen according to where the items exist when they 7918 * are not on screen, then the LayoutManager should return true from 7919 * {@link #supportsPredictiveItemAnimations()} and add additional logic to 7920 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations 7921 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; 7922 * once as a "pre" layout step to determine where items would have been prior to 7923 * a real layout, and again to do the "real" layout. In the pre-layout phase, 7924 * items will remember their pre-layout positions to allow them to be laid out 7925 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will 7926 * be returned from the scrap to help determine correct placement of other items. 7927 * These removed items should not be added to the child list, but should be used 7928 * to help calculate correct positioning of other views, including views that 7929 * were not previously onscreen (referred to as APPEARING views), but whose 7930 * pre-layout offscreen position can be determined given the extra 7931 * information about the pre-layout removed views.</p> 7932 * 7933 * <p>The second layout pass is the real layout in which only non-removed views 7934 * will be used. The only additional requirement during this pass is, if 7935 * {@link #supportsPredictiveItemAnimations()} returns true, to note which 7936 * views exist in the child list prior to layout and which are not there after 7937 * layout (referred to as DISAPPEARING views), and to position/layout those views 7938 * appropriately, without regard to the actual bounds of the RecyclerView. This allows 7939 * the animation system to know the location to which to animate these disappearing 7940 * views.</p> 7941 * 7942 * <p>The default LayoutManager implementations for RecyclerView handle all of these 7943 * requirements for animations already. Clients of RecyclerView can either use one 7944 * of these layout managers directly or look at their implementations of 7945 * onLayoutChildren() to see how they account for the APPEARING and 7946 * DISAPPEARING views.</p> 7947 * 7948 * @param recycler Recycler to use for fetching potentially cached views for a 7949 * position 7950 * @param state Transient state of RecyclerView 7951 */ 7952 public void onLayoutChildren(Recycler recycler, State state) { 7953 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); 7954 } 7955 7956 /** 7957 * Called after a full layout calculation is finished. The layout calculation may include 7958 * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or 7959 * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. 7960 * This method will be called at the end of {@link View#layout(int, int, int, int)} call. 7961 * <p> 7962 * This is a good place for the LayoutManager to do some cleanup like pending scroll 7963 * position, saved state etc. 7964 * 7965 * @param state Transient state of RecyclerView 7966 */ 7967 public void onLayoutCompleted(State state) { 7968 } 7969 7970 /** 7971 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 7972 * 7973 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 7974 * to store extra information specific to the layout. Client code should subclass 7975 * {@link RecyclerView.LayoutParams} for this purpose.</p> 7976 * 7977 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 7978 * you must also override 7979 * {@link #checkLayoutParams(LayoutParams)}, 7980 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 7981 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 7982 * 7983 * @return A new LayoutParams for a child view 7984 */ 7985 public abstract LayoutParams generateDefaultLayoutParams(); 7986 7987 /** 7988 * Determines the validity of the supplied LayoutParams object. 7989 * 7990 * <p>This should check to make sure that the object is of the correct type 7991 * and all values are within acceptable ranges. The default implementation 7992 * returns <code>true</code> for non-null params.</p> 7993 * 7994 * @param lp LayoutParams object to check 7995 * @return true if this LayoutParams object is valid, false otherwise 7996 */ 7997 public boolean checkLayoutParams(LayoutParams lp) { 7998 return lp != null; 7999 } 8000 8001 /** 8002 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 8003 * values from the supplied LayoutParams object if possible. 8004 * 8005 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 8006 * you must also override 8007 * {@link #checkLayoutParams(LayoutParams)}, 8008 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 8009 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 8010 * 8011 * @param lp Source LayoutParams object to copy values from 8012 * @return a new LayoutParams object 8013 */ 8014 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 8015 if (lp instanceof LayoutParams) { 8016 return new LayoutParams((LayoutParams) lp); 8017 } else if (lp instanceof MarginLayoutParams) { 8018 return new LayoutParams((MarginLayoutParams) lp); 8019 } else { 8020 return new LayoutParams(lp); 8021 } 8022 } 8023 8024 /** 8025 * Create a LayoutParams object suitable for this LayoutManager from 8026 * an inflated layout resource. 8027 * 8028 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 8029 * you must also override 8030 * {@link #checkLayoutParams(LayoutParams)}, 8031 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 8032 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 8033 * 8034 * @param c Context for obtaining styled attributes 8035 * @param attrs AttributeSet describing the supplied arguments 8036 * @return a new LayoutParams object 8037 */ 8038 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 8039 return new LayoutParams(c, attrs); 8040 } 8041 8042 /** 8043 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 8044 * The default implementation does nothing and returns 0. 8045 * 8046 * @param dx distance to scroll by in pixels. X increases as scroll position 8047 * approaches the right. 8048 * @param recycler Recycler to use for fetching potentially cached views for a 8049 * position 8050 * @param state Transient state of RecyclerView 8051 * @return The actual distance scrolled. The return value will be negative if dx was 8052 * negative and scrolling proceeeded in that direction. 8053 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 8054 */ 8055 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { 8056 return 0; 8057 } 8058 8059 /** 8060 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 8061 * The default implementation does nothing and returns 0. 8062 * 8063 * @param dy distance to scroll in pixels. Y increases as scroll position 8064 * approaches the bottom. 8065 * @param recycler Recycler to use for fetching potentially cached views for a 8066 * position 8067 * @param state Transient state of RecyclerView 8068 * @return The actual distance scrolled. The return value will be negative if dy was 8069 * negative and scrolling proceeeded in that direction. 8070 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 8071 */ 8072 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { 8073 return 0; 8074 } 8075 8076 /** 8077 * Query if horizontal scrolling is currently supported. The default implementation 8078 * returns false. 8079 * 8080 * @return True if this LayoutManager can scroll the current contents horizontally 8081 */ 8082 public boolean canScrollHorizontally() { 8083 return false; 8084 } 8085 8086 /** 8087 * Query if vertical scrolling is currently supported. The default implementation 8088 * returns false. 8089 * 8090 * @return True if this LayoutManager can scroll the current contents vertically 8091 */ 8092 public boolean canScrollVertically() { 8093 return false; 8094 } 8095 8096 /** 8097 * Scroll to the specified adapter position. 8098 * 8099 * Actual position of the item on the screen depends on the LayoutManager implementation. 8100 * @param position Scroll to this adapter position. 8101 */ 8102 public void scrollToPosition(int position) { 8103 if (DEBUG) { 8104 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); 8105 } 8106 } 8107 8108 /** 8109 * <p>Smooth scroll to the specified adapter position.</p> 8110 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} 8111 * instance and call {@link #startSmoothScroll(SmoothScroller)}. 8112 * </p> 8113 * @param recyclerView The RecyclerView to which this layout manager is attached 8114 * @param state Current State of RecyclerView 8115 * @param position Scroll to this adapter position. 8116 */ 8117 public void smoothScrollToPosition(RecyclerView recyclerView, State state, 8118 int position) { 8119 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); 8120 } 8121 8122 /** 8123 * <p>Starts a smooth scroll using the provided SmoothScroller.</p> 8124 * <p>Calling this method will cancel any previous smooth scroll request.</p> 8125 * @param smoothScroller Instance which defines how smooth scroll should be animated 8126 */ 8127 public void startSmoothScroll(SmoothScroller smoothScroller) { 8128 if (mSmoothScroller != null && smoothScroller != mSmoothScroller 8129 && mSmoothScroller.isRunning()) { 8130 mSmoothScroller.stop(); 8131 } 8132 mSmoothScroller = smoothScroller; 8133 mSmoothScroller.start(mRecyclerView, this); 8134 } 8135 8136 /** 8137 * @return true if RecyclerView is currently in the state of smooth scrolling. 8138 */ 8139 public boolean isSmoothScrolling() { 8140 return mSmoothScroller != null && mSmoothScroller.isRunning(); 8141 } 8142 8143 8144 /** 8145 * Returns the resolved layout direction for this RecyclerView. 8146 * 8147 * @return {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout 8148 * direction is RTL or returns 8149 * {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction 8150 * is not RTL. 8151 */ 8152 public int getLayoutDirection() { 8153 return ViewCompat.getLayoutDirection(mRecyclerView); 8154 } 8155 8156 /** 8157 * Ends all animations on the view created by the {@link ItemAnimator}. 8158 * 8159 * @param view The View for which the animations should be ended. 8160 * @see RecyclerView.ItemAnimator#endAnimations() 8161 */ 8162 public void endAnimation(View view) { 8163 if (mRecyclerView.mItemAnimator != null) { 8164 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); 8165 } 8166 } 8167 8168 /** 8169 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 8170 * to the layout that is known to be going away, either because it has been 8171 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 8172 * visible portion of the container but is being laid out in order to inform RecyclerView 8173 * in how to animate the item out of view. 8174 * <p> 8175 * Views added via this method are going to be invisible to LayoutManager after the 8176 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 8177 * or won't be included in {@link #getChildCount()} method. 8178 * 8179 * @param child View to add and then remove with animation. 8180 */ 8181 public void addDisappearingView(View child) { 8182 addDisappearingView(child, -1); 8183 } 8184 8185 /** 8186 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 8187 * to the layout that is known to be going away, either because it has been 8188 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 8189 * visible portion of the container but is being laid out in order to inform RecyclerView 8190 * in how to animate the item out of view. 8191 * <p> 8192 * Views added via this method are going to be invisible to LayoutManager after the 8193 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 8194 * or won't be included in {@link #getChildCount()} method. 8195 * 8196 * @param child View to add and then remove with animation. 8197 * @param index Index of the view. 8198 */ 8199 public void addDisappearingView(View child, int index) { 8200 addViewInt(child, index, true); 8201 } 8202 8203 /** 8204 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 8205 * use this method to add views obtained from a {@link Recycler} using 8206 * {@link Recycler#getViewForPosition(int)}. 8207 * 8208 * @param child View to add 8209 */ 8210 public void addView(View child) { 8211 addView(child, -1); 8212 } 8213 8214 /** 8215 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 8216 * use this method to add views obtained from a {@link Recycler} using 8217 * {@link Recycler#getViewForPosition(int)}. 8218 * 8219 * @param child View to add 8220 * @param index Index to add child at 8221 */ 8222 public void addView(View child, int index) { 8223 addViewInt(child, index, false); 8224 } 8225 8226 private void addViewInt(View child, int index, boolean disappearing) { 8227 final ViewHolder holder = getChildViewHolderInt(child); 8228 if (disappearing || holder.isRemoved()) { 8229 // these views will be hidden at the end of the layout pass. 8230 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); 8231 } else { 8232 // This may look like unnecessary but may happen if layout manager supports 8233 // predictive layouts and adapter removed then re-added the same item. 8234 // In this case, added version will be visible in the post layout (because add is 8235 // deferred) but RV will still bind it to the same View. 8236 // So if a View re-appears in post layout pass, remove it from disappearing list. 8237 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); 8238 } 8239 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 8240 if (holder.wasReturnedFromScrap() || holder.isScrap()) { 8241 if (holder.isScrap()) { 8242 holder.unScrap(); 8243 } else { 8244 holder.clearReturnedFromScrapFlag(); 8245 } 8246 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); 8247 if (DISPATCH_TEMP_DETACH) { 8248 ViewCompat.dispatchFinishTemporaryDetach(child); 8249 } 8250 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child 8251 // ensure in correct position 8252 int currentIndex = mChildHelper.indexOfChild(child); 8253 if (index == -1) { 8254 index = mChildHelper.getChildCount(); 8255 } 8256 if (currentIndex == -1) { 8257 throw new IllegalStateException("Added View has RecyclerView as parent but" 8258 + " view is not a real child. Unfiltered index:" 8259 + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel()); 8260 } 8261 if (currentIndex != index) { 8262 mRecyclerView.mLayout.moveView(currentIndex, index); 8263 } 8264 } else { 8265 mChildHelper.addView(child, index, false); 8266 lp.mInsetsDirty = true; 8267 if (mSmoothScroller != null && mSmoothScroller.isRunning()) { 8268 mSmoothScroller.onChildAttachedToWindow(child); 8269 } 8270 } 8271 if (lp.mPendingInvalidate) { 8272 if (DEBUG) { 8273 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); 8274 } 8275 holder.itemView.invalidate(); 8276 lp.mPendingInvalidate = false; 8277 } 8278 } 8279 8280 /** 8281 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 8282 * use this method to completely remove a child view that is no longer needed. 8283 * LayoutManagers should strongly consider recycling removed views using 8284 * {@link Recycler#recycleView(android.view.View)}. 8285 * 8286 * @param child View to remove 8287 */ 8288 public void removeView(View child) { 8289 mChildHelper.removeView(child); 8290 } 8291 8292 /** 8293 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 8294 * use this method to completely remove a child view that is no longer needed. 8295 * LayoutManagers should strongly consider recycling removed views using 8296 * {@link Recycler#recycleView(android.view.View)}. 8297 * 8298 * @param index Index of the child view to remove 8299 */ 8300 public void removeViewAt(int index) { 8301 final View child = getChildAt(index); 8302 if (child != null) { 8303 mChildHelper.removeViewAt(index); 8304 } 8305 } 8306 8307 /** 8308 * Remove all views from the currently attached RecyclerView. This will not recycle 8309 * any of the affected views; the LayoutManager is responsible for doing so if desired. 8310 */ 8311 public void removeAllViews() { 8312 // Only remove non-animating views 8313 final int childCount = getChildCount(); 8314 for (int i = childCount - 1; i >= 0; i--) { 8315 mChildHelper.removeViewAt(i); 8316 } 8317 } 8318 8319 /** 8320 * Returns offset of the RecyclerView's text baseline from the its top boundary. 8321 * 8322 * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if 8323 * there is no baseline. 8324 */ 8325 public int getBaseline() { 8326 return -1; 8327 } 8328 8329 /** 8330 * Returns the adapter position of the item represented by the given View. This does not 8331 * contain any adapter changes that might have happened after the last layout. 8332 * 8333 * @param view The view to query 8334 * @return The adapter position of the item which is rendered by this View. 8335 */ 8336 public int getPosition(@NonNull View view) { 8337 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 8338 } 8339 8340 /** 8341 * Returns the View type defined by the adapter. 8342 * 8343 * @param view The view to query 8344 * @return The type of the view assigned by the adapter. 8345 */ 8346 public int getItemViewType(@NonNull View view) { 8347 return getChildViewHolderInt(view).getItemViewType(); 8348 } 8349 8350 /** 8351 * Traverses the ancestors of the given view and returns the item view that contains it 8352 * and also a direct child of the LayoutManager. 8353 * <p> 8354 * Note that this method may return null if the view is a child of the RecyclerView but 8355 * not a child of the LayoutManager (e.g. running a disappear animation). 8356 * 8357 * @param view The view that is a descendant of the LayoutManager. 8358 * 8359 * @return The direct child of the LayoutManager which contains the given view or null if 8360 * the provided view is not a descendant of this LayoutManager. 8361 * 8362 * @see RecyclerView#getChildViewHolder(View) 8363 * @see RecyclerView#findContainingViewHolder(View) 8364 */ 8365 @Nullable 8366 public View findContainingItemView(@NonNull View view) { 8367 if (mRecyclerView == null) { 8368 return null; 8369 } 8370 View found = mRecyclerView.findContainingItemView(view); 8371 if (found == null) { 8372 return null; 8373 } 8374 if (mChildHelper.isHidden(found)) { 8375 return null; 8376 } 8377 return found; 8378 } 8379 8380 /** 8381 * Finds the view which represents the given adapter position. 8382 * <p> 8383 * This method traverses each child since it has no information about child order. 8384 * Override this method to improve performance if your LayoutManager keeps data about 8385 * child views. 8386 * <p> 8387 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. 8388 * 8389 * @param position Position of the item in adapter 8390 * @return The child view that represents the given position or null if the position is not 8391 * laid out 8392 */ 8393 @Nullable 8394 public View findViewByPosition(int position) { 8395 final int childCount = getChildCount(); 8396 for (int i = 0; i < childCount; i++) { 8397 View child = getChildAt(i); 8398 ViewHolder vh = getChildViewHolderInt(child); 8399 if (vh == null) { 8400 continue; 8401 } 8402 if (vh.getLayoutPosition() == position && !vh.shouldIgnore() 8403 && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { 8404 return child; 8405 } 8406 } 8407 return null; 8408 } 8409 8410 /** 8411 * Temporarily detach a child view. 8412 * 8413 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 8414 * views currently attached to the RecyclerView. Generally LayoutManager implementations 8415 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 8416 * so that the detached view may be rebound and reused.</p> 8417 * 8418 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 8419 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 8420 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 8421 * before the LayoutManager entry point method called by RecyclerView returns.</p> 8422 * 8423 * @param child Child to detach 8424 */ 8425 public void detachView(@NonNull View child) { 8426 final int ind = mChildHelper.indexOfChild(child); 8427 if (ind >= 0) { 8428 detachViewInternal(ind, child); 8429 } 8430 } 8431 8432 /** 8433 * Temporarily detach a child view. 8434 * 8435 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 8436 * views currently attached to the RecyclerView. Generally LayoutManager implementations 8437 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 8438 * so that the detached view may be rebound and reused.</p> 8439 * 8440 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 8441 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 8442 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 8443 * before the LayoutManager entry point method called by RecyclerView returns.</p> 8444 * 8445 * @param index Index of the child to detach 8446 */ 8447 public void detachViewAt(int index) { 8448 detachViewInternal(index, getChildAt(index)); 8449 } 8450 8451 private void detachViewInternal(int index, @NonNull View view) { 8452 if (DISPATCH_TEMP_DETACH) { 8453 ViewCompat.dispatchStartTemporaryDetach(view); 8454 } 8455 mChildHelper.detachViewFromParent(index); 8456 } 8457 8458 /** 8459 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8460 * This method should not be used to reattach views that were previously 8461 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8462 * 8463 * @param child Child to reattach 8464 * @param index Intended child index for child 8465 * @param lp LayoutParams for child 8466 */ 8467 public void attachView(@NonNull View child, int index, LayoutParams lp) { 8468 ViewHolder vh = getChildViewHolderInt(child); 8469 if (vh.isRemoved()) { 8470 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); 8471 } else { 8472 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); 8473 } 8474 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); 8475 if (DISPATCH_TEMP_DETACH) { 8476 ViewCompat.dispatchFinishTemporaryDetach(child); 8477 } 8478 } 8479 8480 /** 8481 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8482 * This method should not be used to reattach views that were previously 8483 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8484 * 8485 * @param child Child to reattach 8486 * @param index Intended child index for child 8487 */ 8488 public void attachView(@NonNull View child, int index) { 8489 attachView(child, index, (LayoutParams) child.getLayoutParams()); 8490 } 8491 8492 /** 8493 * Reattach a previously {@link #detachView(android.view.View) detached} view. 8494 * This method should not be used to reattach views that were previously 8495 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 8496 * 8497 * @param child Child to reattach 8498 */ 8499 public void attachView(@NonNull View child) { 8500 attachView(child, -1); 8501 } 8502 8503 /** 8504 * Finish removing a view that was previously temporarily 8505 * {@link #detachView(android.view.View) detached}. 8506 * 8507 * @param child Detached child to remove 8508 */ 8509 public void removeDetachedView(@NonNull View child) { 8510 mRecyclerView.removeDetachedView(child, false); 8511 } 8512 8513 /** 8514 * Moves a View from one position to another. 8515 * 8516 * @param fromIndex The View's initial index 8517 * @param toIndex The View's target index 8518 */ 8519 public void moveView(int fromIndex, int toIndex) { 8520 View view = getChildAt(fromIndex); 8521 if (view == null) { 8522 throw new IllegalArgumentException("Cannot move a child from non-existing index:" 8523 + fromIndex + mRecyclerView.toString()); 8524 } 8525 detachViewAt(fromIndex); 8526 attachView(view, toIndex); 8527 } 8528 8529 /** 8530 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 8531 * 8532 * <p>Scrapping a view allows it to be rebound and reused to show updated or 8533 * different data.</p> 8534 * 8535 * @param child Child to detach and scrap 8536 * @param recycler Recycler to deposit the new scrap view into 8537 */ 8538 public void detachAndScrapView(@NonNull View child, @NonNull Recycler recycler) { 8539 int index = mChildHelper.indexOfChild(child); 8540 scrapOrRecycleView(recycler, index, child); 8541 } 8542 8543 /** 8544 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 8545 * 8546 * <p>Scrapping a view allows it to be rebound and reused to show updated or 8547 * different data.</p> 8548 * 8549 * @param index Index of child to detach and scrap 8550 * @param recycler Recycler to deposit the new scrap view into 8551 */ 8552 public void detachAndScrapViewAt(int index, @NonNull Recycler recycler) { 8553 final View child = getChildAt(index); 8554 scrapOrRecycleView(recycler, index, child); 8555 } 8556 8557 /** 8558 * Remove a child view and recycle it using the given Recycler. 8559 * 8560 * @param child Child to remove and recycle 8561 * @param recycler Recycler to use to recycle child 8562 */ 8563 public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) { 8564 removeView(child); 8565 recycler.recycleView(child); 8566 } 8567 8568 /** 8569 * Remove a child view and recycle it using the given Recycler. 8570 * 8571 * @param index Index of child to remove and recycle 8572 * @param recycler Recycler to use to recycle child 8573 */ 8574 public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { 8575 final View view = getChildAt(index); 8576 removeViewAt(index); 8577 recycler.recycleView(view); 8578 } 8579 8580 /** 8581 * Return the current number of child views attached to the parent RecyclerView. 8582 * This does not include child views that were temporarily detached and/or scrapped. 8583 * 8584 * @return Number of attached children 8585 */ 8586 public int getChildCount() { 8587 return mChildHelper != null ? mChildHelper.getChildCount() : 0; 8588 } 8589 8590 /** 8591 * Return the child view at the given index 8592 * @param index Index of child to return 8593 * @return Child view at index 8594 */ 8595 @Nullable 8596 public View getChildAt(int index) { 8597 return mChildHelper != null ? mChildHelper.getChildAt(index) : null; 8598 } 8599 8600 /** 8601 * Return the width measurement spec mode that is currently relevant to the LayoutManager. 8602 * 8603 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 8604 * {@link #setAutoMeasureEnabled(boolean)}. 8605 * 8606 * <p>When RecyclerView is running a layout, this value is always set to 8607 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 8608 * 8609 * @return Width measure spec mode 8610 * 8611 * @see View.MeasureSpec#getMode(int) 8612 */ 8613 public int getWidthMode() { 8614 return mWidthMode; 8615 } 8616 8617 /** 8618 * Return the height measurement spec mode that is currently relevant to the LayoutManager. 8619 * 8620 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 8621 * {@link #setAutoMeasureEnabled(boolean)}. 8622 * 8623 * <p>When RecyclerView is running a layout, this value is always set to 8624 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 8625 * 8626 * @return Height measure spec mode 8627 * 8628 * @see View.MeasureSpec#getMode(int) 8629 */ 8630 public int getHeightMode() { 8631 return mHeightMode; 8632 } 8633 8634 /** 8635 * Returns the width that is currently relevant to the LayoutManager. 8636 * 8637 * <p>This value is usually equal to the laid out width of the {@link RecyclerView} but may 8638 * reflect the current {@link android.view.View.MeasureSpec} width if the 8639 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 8640 * measuring. The LayoutManager must always use this method to retrieve the width relevant 8641 * to it at any given time. 8642 * 8643 * @return Width in pixels 8644 */ 8645 @Px 8646 public int getWidth() { 8647 return mWidth; 8648 } 8649 8650 /** 8651 * Returns the height that is currently relevant to the LayoutManager. 8652 * 8653 * <p>This value is usually equal to the laid out height of the {@link RecyclerView} but may 8654 * reflect the current {@link android.view.View.MeasureSpec} height if the 8655 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 8656 * measuring. The LayoutManager must always use this method to retrieve the height relevant 8657 * to it at any given time. 8658 * 8659 * @return Height in pixels 8660 */ 8661 @Px 8662 public int getHeight() { 8663 return mHeight; 8664 } 8665 8666 /** 8667 * Return the left padding of the parent RecyclerView 8668 * 8669 * @return Padding in pixels 8670 */ 8671 @Px 8672 public int getPaddingLeft() { 8673 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 8674 } 8675 8676 /** 8677 * Return the top padding of the parent RecyclerView 8678 * 8679 * @return Padding in pixels 8680 */ 8681 @Px 8682 public int getPaddingTop() { 8683 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 8684 } 8685 8686 /** 8687 * Return the right padding of the parent RecyclerView 8688 * 8689 * @return Padding in pixels 8690 */ 8691 @Px 8692 public int getPaddingRight() { 8693 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 8694 } 8695 8696 /** 8697 * Return the bottom padding of the parent RecyclerView 8698 * 8699 * @return Padding in pixels 8700 */ 8701 @Px 8702 public int getPaddingBottom() { 8703 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 8704 } 8705 8706 /** 8707 * Return the start padding of the parent RecyclerView 8708 * 8709 * @return Padding in pixels 8710 */ 8711 @Px 8712 public int getPaddingStart() { 8713 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 8714 } 8715 8716 /** 8717 * Return the end padding of the parent RecyclerView 8718 * 8719 * @return Padding in pixels 8720 */ 8721 @Px 8722 public int getPaddingEnd() { 8723 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 8724 } 8725 8726 /** 8727 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 8728 * 8729 * @return True if the RecyclerView has focus, false otherwise. 8730 * @see View#isFocused() 8731 */ 8732 public boolean isFocused() { 8733 return mRecyclerView != null && mRecyclerView.isFocused(); 8734 } 8735 8736 /** 8737 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 8738 * 8739 * @return true if the RecyclerView has or contains focus 8740 * @see View#hasFocus() 8741 */ 8742 public boolean hasFocus() { 8743 return mRecyclerView != null && mRecyclerView.hasFocus(); 8744 } 8745 8746 /** 8747 * Returns the item View which has or contains focus. 8748 * 8749 * @return A direct child of RecyclerView which has focus or contains the focused child. 8750 */ 8751 @Nullable 8752 public View getFocusedChild() { 8753 if (mRecyclerView == null) { 8754 return null; 8755 } 8756 final View focused = mRecyclerView.getFocusedChild(); 8757 if (focused == null || mChildHelper.isHidden(focused)) { 8758 return null; 8759 } 8760 return focused; 8761 } 8762 8763 /** 8764 * Returns the number of items in the adapter bound to the parent RecyclerView. 8765 * <p> 8766 * Note that this number is not necessarily equal to 8767 * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is 8768 * available, you should use {@link State#getItemCount() State#getItemCount()} instead. 8769 * For more details, check the documentation for 8770 * {@link State#getItemCount() State#getItemCount()}. 8771 * 8772 * @return The number of items in the bound adapter 8773 * @see State#getItemCount() 8774 */ 8775 public int getItemCount() { 8776 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 8777 return a != null ? a.getItemCount() : 0; 8778 } 8779 8780 /** 8781 * Offset all child views attached to the parent RecyclerView by dx pixels along 8782 * the horizontal axis. 8783 * 8784 * @param dx Pixels to offset by 8785 */ 8786 public void offsetChildrenHorizontal(@Px int dx) { 8787 if (mRecyclerView != null) { 8788 mRecyclerView.offsetChildrenHorizontal(dx); 8789 } 8790 } 8791 8792 /** 8793 * Offset all child views attached to the parent RecyclerView by dy pixels along 8794 * the vertical axis. 8795 * 8796 * @param dy Pixels to offset by 8797 */ 8798 public void offsetChildrenVertical(@Px int dy) { 8799 if (mRecyclerView != null) { 8800 mRecyclerView.offsetChildrenVertical(dy); 8801 } 8802 } 8803 8804 /** 8805 * Flags a view so that it will not be scrapped or recycled. 8806 * <p> 8807 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and 8808 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child 8809 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not 8810 * ignore the child. 8811 * <p> 8812 * Before this child can be recycled again, you have to call 8813 * {@link #stopIgnoringView(View)}. 8814 * <p> 8815 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 8816 * 8817 * @param view View to ignore. 8818 * @see #stopIgnoringView(View) 8819 */ 8820 public void ignoreView(@NonNull View view) { 8821 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { 8822 // checking this because calling this method on a recycled or detached view may 8823 // cause loss of state. 8824 throw new IllegalArgumentException("View should be fully attached to be ignored" 8825 + mRecyclerView.exceptionLabel()); 8826 } 8827 final ViewHolder vh = getChildViewHolderInt(view); 8828 vh.addFlags(ViewHolder.FLAG_IGNORE); 8829 mRecyclerView.mViewInfoStore.removeViewHolder(vh); 8830 } 8831 8832 /** 8833 * View can be scrapped and recycled again. 8834 * <p> 8835 * Note that calling this method removes all information in the view holder. 8836 * <p> 8837 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 8838 * 8839 * @param view View to ignore. 8840 */ 8841 public void stopIgnoringView(@NonNull View view) { 8842 final ViewHolder vh = getChildViewHolderInt(view); 8843 vh.stopIgnoring(); 8844 vh.resetInternal(); 8845 vh.addFlags(ViewHolder.FLAG_INVALID); 8846 } 8847 8848 /** 8849 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 8850 * into the given Recycler. The Recycler may prefer to reuse scrap views before 8851 * other views that were previously recycled. 8852 * 8853 * @param recycler Recycler to scrap views into 8854 */ 8855 public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { 8856 final int childCount = getChildCount(); 8857 for (int i = childCount - 1; i >= 0; i--) { 8858 final View v = getChildAt(i); 8859 scrapOrRecycleView(recycler, i, v); 8860 } 8861 } 8862 8863 private void scrapOrRecycleView(Recycler recycler, int index, View view) { 8864 final ViewHolder viewHolder = getChildViewHolderInt(view); 8865 if (viewHolder.shouldIgnore()) { 8866 if (DEBUG) { 8867 Log.d(TAG, "ignoring view " + viewHolder); 8868 } 8869 return; 8870 } 8871 if (viewHolder.isInvalid() && !viewHolder.isRemoved() 8872 && !mRecyclerView.mAdapter.hasStableIds()) { 8873 removeViewAt(index); 8874 recycler.recycleViewHolderInternal(viewHolder); 8875 } else { 8876 detachViewAt(index); 8877 recycler.scrapView(view); 8878 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); 8879 } 8880 } 8881 8882 /** 8883 * Recycles the scrapped views. 8884 * <p> 8885 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is 8886 * the expected behavior if scrapped views are used for animations. Otherwise, we need to 8887 * call remove and invalidate RecyclerView to ensure UI update. 8888 * 8889 * @param recycler Recycler 8890 */ 8891 void removeAndRecycleScrapInt(Recycler recycler) { 8892 final int scrapCount = recycler.getScrapCount(); 8893 // Loop backward, recycler might be changed by removeDetachedView() 8894 for (int i = scrapCount - 1; i >= 0; i--) { 8895 final View scrap = recycler.getScrapViewAt(i); 8896 final ViewHolder vh = getChildViewHolderInt(scrap); 8897 if (vh.shouldIgnore()) { 8898 continue; 8899 } 8900 // If the scrap view is animating, we need to cancel them first. If we cancel it 8901 // here, ItemAnimator callback may recycle it which will cause double recycling. 8902 // To avoid this, we mark it as not recycleable before calling the item animator. 8903 // Since removeDetachedView calls a user API, a common mistake (ending animations on 8904 // the view) may recycle it too, so we guard it before we call user APIs. 8905 vh.setIsRecyclable(false); 8906 if (vh.isTmpDetached()) { 8907 mRecyclerView.removeDetachedView(scrap, false); 8908 } 8909 if (mRecyclerView.mItemAnimator != null) { 8910 mRecyclerView.mItemAnimator.endAnimation(vh); 8911 } 8912 vh.setIsRecyclable(true); 8913 recycler.quickRecycleScrapView(scrap); 8914 } 8915 recycler.clearScrap(); 8916 if (scrapCount > 0) { 8917 mRecyclerView.invalidate(); 8918 } 8919 } 8920 8921 8922 /** 8923 * Measure a child view using standard measurement policy, taking the padding 8924 * of the parent RecyclerView and any added item decorations into account. 8925 * 8926 * <p>If the RecyclerView can be scrolled in either dimension the caller may 8927 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 8928 * 8929 * @param child Child view to measure 8930 * @param widthUsed Width in pixels currently consumed by other views, if relevant 8931 * @param heightUsed Height in pixels currently consumed by other views, if relevant 8932 */ 8933 public void measureChild(@NonNull View child, int widthUsed, int heightUsed) { 8934 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 8935 8936 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 8937 widthUsed += insets.left + insets.right; 8938 heightUsed += insets.top + insets.bottom; 8939 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 8940 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 8941 canScrollHorizontally()); 8942 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 8943 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 8944 canScrollVertically()); 8945 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 8946 child.measure(widthSpec, heightSpec); 8947 } 8948 } 8949 8950 /** 8951 * RecyclerView internally does its own View measurement caching which should help with 8952 * WRAP_CONTENT. 8953 * <p> 8954 * Use this method if the View is already measured once in this layout pass. 8955 */ 8956 boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 8957 return !mMeasurementCacheEnabled 8958 || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) 8959 || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); 8960 } 8961 8962 // we may consider making this public 8963 /** 8964 * RecyclerView internally does its own View measurement caching which should help with 8965 * WRAP_CONTENT. 8966 * <p> 8967 * Use this method if the View is not yet measured and you need to decide whether to 8968 * measure this View or not. 8969 */ 8970 boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 8971 return child.isLayoutRequested() 8972 || !mMeasurementCacheEnabled 8973 || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) 8974 || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); 8975 } 8976 8977 /** 8978 * In addition to the View Framework's measurement cache, RecyclerView uses its own 8979 * additional measurement cache for its children to avoid re-measuring them when not 8980 * necessary. It is on by default but it can be turned off via 8981 * {@link #setMeasurementCacheEnabled(boolean)}. 8982 * 8983 * @return True if measurement cache is enabled, false otherwise. 8984 * 8985 * @see #setMeasurementCacheEnabled(boolean) 8986 */ 8987 public boolean isMeasurementCacheEnabled() { 8988 return mMeasurementCacheEnabled; 8989 } 8990 8991 /** 8992 * Sets whether RecyclerView should use its own measurement cache for the children. This is 8993 * a more aggressive cache than the framework uses. 8994 * 8995 * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. 8996 * 8997 * @see #isMeasurementCacheEnabled() 8998 */ 8999 public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { 9000 mMeasurementCacheEnabled = measurementCacheEnabled; 9001 } 9002 9003 private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { 9004 final int specMode = MeasureSpec.getMode(spec); 9005 final int specSize = MeasureSpec.getSize(spec); 9006 if (dimension > 0 && childSize != dimension) { 9007 return false; 9008 } 9009 switch (specMode) { 9010 case MeasureSpec.UNSPECIFIED: 9011 return true; 9012 case MeasureSpec.AT_MOST: 9013 return specSize >= childSize; 9014 case MeasureSpec.EXACTLY: 9015 return specSize == childSize; 9016 } 9017 return false; 9018 } 9019 9020 /** 9021 * Measure a child view using standard measurement policy, taking the padding 9022 * of the parent RecyclerView, any added item decorations and the child margins 9023 * into account. 9024 * 9025 * <p>If the RecyclerView can be scrolled in either dimension the caller may 9026 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 9027 * 9028 * @param child Child view to measure 9029 * @param widthUsed Width in pixels currently consumed by other views, if relevant 9030 * @param heightUsed Height in pixels currently consumed by other views, if relevant 9031 */ 9032 public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { 9033 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 9034 9035 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 9036 widthUsed += insets.left + insets.right; 9037 heightUsed += insets.top + insets.bottom; 9038 9039 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 9040 getPaddingLeft() + getPaddingRight() 9041 + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 9042 canScrollHorizontally()); 9043 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 9044 getPaddingTop() + getPaddingBottom() 9045 + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 9046 canScrollVertically()); 9047 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 9048 child.measure(widthSpec, heightSpec); 9049 } 9050 } 9051 9052 /** 9053 * Calculate a MeasureSpec value for measuring a child view in one dimension. 9054 * 9055 * @param parentSize Size of the parent view where the child will be placed 9056 * @param padding Total space currently consumed by other elements of the parent 9057 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 9058 * Generally obtained from the child view's LayoutParams 9059 * @param canScroll true if the parent RecyclerView can scroll in this dimension 9060 * 9061 * @return a MeasureSpec value for the child view 9062 * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} 9063 */ 9064 @Deprecated 9065 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 9066 boolean canScroll) { 9067 int size = Math.max(0, parentSize - padding); 9068 int resultSize = 0; 9069 int resultMode = 0; 9070 if (canScroll) { 9071 if (childDimension >= 0) { 9072 resultSize = childDimension; 9073 resultMode = MeasureSpec.EXACTLY; 9074 } else { 9075 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 9076 // instead using UNSPECIFIED. 9077 resultSize = 0; 9078 resultMode = MeasureSpec.UNSPECIFIED; 9079 } 9080 } else { 9081 if (childDimension >= 0) { 9082 resultSize = childDimension; 9083 resultMode = MeasureSpec.EXACTLY; 9084 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9085 resultSize = size; 9086 // TODO this should be my spec. 9087 resultMode = MeasureSpec.EXACTLY; 9088 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9089 resultSize = size; 9090 resultMode = MeasureSpec.AT_MOST; 9091 } 9092 } 9093 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 9094 } 9095 9096 /** 9097 * Calculate a MeasureSpec value for measuring a child view in one dimension. 9098 * 9099 * @param parentSize Size of the parent view where the child will be placed 9100 * @param parentMode The measurement spec mode of the parent 9101 * @param padding Total space currently consumed by other elements of parent 9102 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 9103 * Generally obtained from the child view's LayoutParams 9104 * @param canScroll true if the parent RecyclerView can scroll in this dimension 9105 * 9106 * @return a MeasureSpec value for the child view 9107 */ 9108 public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, 9109 int childDimension, boolean canScroll) { 9110 int size = Math.max(0, parentSize - padding); 9111 int resultSize = 0; 9112 int resultMode = 0; 9113 if (canScroll) { 9114 if (childDimension >= 0) { 9115 resultSize = childDimension; 9116 resultMode = MeasureSpec.EXACTLY; 9117 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9118 switch (parentMode) { 9119 case MeasureSpec.AT_MOST: 9120 case MeasureSpec.EXACTLY: 9121 resultSize = size; 9122 resultMode = parentMode; 9123 break; 9124 case MeasureSpec.UNSPECIFIED: 9125 resultSize = 0; 9126 resultMode = MeasureSpec.UNSPECIFIED; 9127 break; 9128 } 9129 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9130 resultSize = 0; 9131 resultMode = MeasureSpec.UNSPECIFIED; 9132 } 9133 } else { 9134 if (childDimension >= 0) { 9135 resultSize = childDimension; 9136 resultMode = MeasureSpec.EXACTLY; 9137 } else if (childDimension == LayoutParams.MATCH_PARENT) { 9138 resultSize = size; 9139 resultMode = parentMode; 9140 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 9141 resultSize = size; 9142 if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { 9143 resultMode = MeasureSpec.AT_MOST; 9144 } else { 9145 resultMode = MeasureSpec.UNSPECIFIED; 9146 } 9147 9148 } 9149 } 9150 //noinspection WrongConstant 9151 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 9152 } 9153 9154 /** 9155 * Returns the measured width of the given child, plus the additional size of 9156 * any insets applied by {@link ItemDecoration ItemDecorations}. 9157 * 9158 * @param child Child view to query 9159 * @return child's measured width plus <code>ItemDecoration</code> insets 9160 * 9161 * @see View#getMeasuredWidth() 9162 */ 9163 public int getDecoratedMeasuredWidth(@NonNull View child) { 9164 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9165 return child.getMeasuredWidth() + insets.left + insets.right; 9166 } 9167 9168 /** 9169 * Returns the measured height of the given child, plus the additional size of 9170 * any insets applied by {@link ItemDecoration ItemDecorations}. 9171 * 9172 * @param child Child view to query 9173 * @return child's measured height plus <code>ItemDecoration</code> insets 9174 * 9175 * @see View#getMeasuredHeight() 9176 */ 9177 public int getDecoratedMeasuredHeight(@NonNull View child) { 9178 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9179 return child.getMeasuredHeight() + insets.top + insets.bottom; 9180 } 9181 9182 /** 9183 * Lay out the given child view within the RecyclerView using coordinates that 9184 * include any current {@link ItemDecoration ItemDecorations}. 9185 * 9186 * <p>LayoutManagers should prefer working in sizes and coordinates that include 9187 * item decoration insets whenever possible. This allows the LayoutManager to effectively 9188 * ignore decoration insets within measurement and layout code. See the following 9189 * methods:</p> 9190 * <ul> 9191 * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li> 9192 * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li> 9193 * <li>{@link #measureChild(View, int, int)}</li> 9194 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 9195 * <li>{@link #getDecoratedLeft(View)}</li> 9196 * <li>{@link #getDecoratedTop(View)}</li> 9197 * <li>{@link #getDecoratedRight(View)}</li> 9198 * <li>{@link #getDecoratedBottom(View)}</li> 9199 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 9200 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 9201 * </ul> 9202 * 9203 * @param child Child to lay out 9204 * @param left Left edge, with item decoration insets included 9205 * @param top Top edge, with item decoration insets included 9206 * @param right Right edge, with item decoration insets included 9207 * @param bottom Bottom edge, with item decoration insets included 9208 * 9209 * @see View#layout(int, int, int, int) 9210 * @see #layoutDecoratedWithMargins(View, int, int, int, int) 9211 */ 9212 public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) { 9213 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9214 child.layout(left + insets.left, top + insets.top, right - insets.right, 9215 bottom - insets.bottom); 9216 } 9217 9218 /** 9219 * Lay out the given child view within the RecyclerView using coordinates that 9220 * include any current {@link ItemDecoration ItemDecorations} and margins. 9221 * 9222 * <p>LayoutManagers should prefer working in sizes and coordinates that include 9223 * item decoration insets whenever possible. This allows the LayoutManager to effectively 9224 * ignore decoration insets within measurement and layout code. See the following 9225 * methods:</p> 9226 * <ul> 9227 * <li>{@link #layoutDecorated(View, int, int, int, int)}</li> 9228 * <li>{@link #measureChild(View, int, int)}</li> 9229 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 9230 * <li>{@link #getDecoratedLeft(View)}</li> 9231 * <li>{@link #getDecoratedTop(View)}</li> 9232 * <li>{@link #getDecoratedRight(View)}</li> 9233 * <li>{@link #getDecoratedBottom(View)}</li> 9234 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 9235 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 9236 * </ul> 9237 * 9238 * @param child Child to lay out 9239 * @param left Left edge, with item decoration insets and left margin included 9240 * @param top Top edge, with item decoration insets and top margin included 9241 * @param right Right edge, with item decoration insets and right margin included 9242 * @param bottom Bottom edge, with item decoration insets and bottom margin included 9243 * 9244 * @see View#layout(int, int, int, int) 9245 * @see #layoutDecorated(View, int, int, int, int) 9246 */ 9247 public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, 9248 int bottom) { 9249 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 9250 final Rect insets = lp.mDecorInsets; 9251 child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, 9252 right - insets.right - lp.rightMargin, 9253 bottom - insets.bottom - lp.bottomMargin); 9254 } 9255 9256 /** 9257 * Calculates the bounding box of the View while taking into account its matrix changes 9258 * (translation, scale etc) with respect to the RecyclerView. 9259 * <p> 9260 * If {@code includeDecorInsets} is {@code true}, they are applied first before applying 9261 * the View's matrix so that the decor offsets also go through the same transformation. 9262 * 9263 * @param child The ItemView whose bounding box should be calculated. 9264 * @param includeDecorInsets True if the decor insets should be included in the bounding box 9265 * @param out The rectangle into which the output will be written. 9266 */ 9267 public void getTransformedBoundingBox(@NonNull View child, boolean includeDecorInsets, 9268 @NonNull Rect out) { 9269 if (includeDecorInsets) { 9270 Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 9271 out.set(-insets.left, -insets.top, 9272 child.getWidth() + insets.right, child.getHeight() + insets.bottom); 9273 } else { 9274 out.set(0, 0, child.getWidth(), child.getHeight()); 9275 } 9276 9277 if (mRecyclerView != null) { 9278 final Matrix childMatrix = child.getMatrix(); 9279 if (childMatrix != null && !childMatrix.isIdentity()) { 9280 final RectF tempRectF = mRecyclerView.mTempRectF; 9281 tempRectF.set(out); 9282 childMatrix.mapRect(tempRectF); 9283 out.set( 9284 (int) Math.floor(tempRectF.left), 9285 (int) Math.floor(tempRectF.top), 9286 (int) Math.ceil(tempRectF.right), 9287 (int) Math.ceil(tempRectF.bottom) 9288 ); 9289 } 9290 } 9291 out.offset(child.getLeft(), child.getTop()); 9292 } 9293 9294 /** 9295 * Returns the bounds of the view including its decoration and margins. 9296 * 9297 * @param view The view element to check 9298 * @param outBounds A rect that will receive the bounds of the element including its 9299 * decoration and margins. 9300 */ 9301 public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { 9302 RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); 9303 } 9304 9305 /** 9306 * Returns the left edge of the given child view within its parent, offset by any applied 9307 * {@link ItemDecoration ItemDecorations}. 9308 * 9309 * @param child Child to query 9310 * @return Child left edge with offsets applied 9311 * @see #getLeftDecorationWidth(View) 9312 */ 9313 public int getDecoratedLeft(@NonNull View child) { 9314 return child.getLeft() - getLeftDecorationWidth(child); 9315 } 9316 9317 /** 9318 * Returns the top edge of the given child view within its parent, offset by any applied 9319 * {@link ItemDecoration ItemDecorations}. 9320 * 9321 * @param child Child to query 9322 * @return Child top edge with offsets applied 9323 * @see #getTopDecorationHeight(View) 9324 */ 9325 public int getDecoratedTop(@NonNull View child) { 9326 return child.getTop() - getTopDecorationHeight(child); 9327 } 9328 9329 /** 9330 * Returns the right edge of the given child view within its parent, offset by any applied 9331 * {@link ItemDecoration ItemDecorations}. 9332 * 9333 * @param child Child to query 9334 * @return Child right edge with offsets applied 9335 * @see #getRightDecorationWidth(View) 9336 */ 9337 public int getDecoratedRight(@NonNull View child) { 9338 return child.getRight() + getRightDecorationWidth(child); 9339 } 9340 9341 /** 9342 * Returns the bottom edge of the given child view within its parent, offset by any applied 9343 * {@link ItemDecoration ItemDecorations}. 9344 * 9345 * @param child Child to query 9346 * @return Child bottom edge with offsets applied 9347 * @see #getBottomDecorationHeight(View) 9348 */ 9349 public int getDecoratedBottom(@NonNull View child) { 9350 return child.getBottom() + getBottomDecorationHeight(child); 9351 } 9352 9353 /** 9354 * Calculates the item decor insets applied to the given child and updates the provided 9355 * Rect instance with the inset values. 9356 * <ul> 9357 * <li>The Rect's left is set to the total width of left decorations.</li> 9358 * <li>The Rect's top is set to the total height of top decorations.</li> 9359 * <li>The Rect's right is set to the total width of right decorations.</li> 9360 * <li>The Rect's bottom is set to total height of bottom decorations.</li> 9361 * </ul> 9362 * <p> 9363 * Note that item decorations are automatically calculated when one of the LayoutManager's 9364 * measure child methods is called. If you need to measure the child with custom specs via 9365 * {@link View#measure(int, int)}, you can use this method to get decorations. 9366 * 9367 * @param child The child view whose decorations should be calculated 9368 * @param outRect The Rect to hold result values 9369 */ 9370 public void calculateItemDecorationsForChild(@NonNull View child, @NonNull Rect outRect) { 9371 if (mRecyclerView == null) { 9372 outRect.set(0, 0, 0, 0); 9373 return; 9374 } 9375 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 9376 outRect.set(insets); 9377 } 9378 9379 /** 9380 * Returns the total height of item decorations applied to child's top. 9381 * <p> 9382 * Note that this value is not updated until the View is measured or 9383 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9384 * 9385 * @param child Child to query 9386 * @return The total height of item decorations applied to the child's top. 9387 * @see #getDecoratedTop(View) 9388 * @see #calculateItemDecorationsForChild(View, Rect) 9389 */ 9390 public int getTopDecorationHeight(@NonNull View child) { 9391 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; 9392 } 9393 9394 /** 9395 * Returns the total height of item decorations applied to child's bottom. 9396 * <p> 9397 * Note that this value is not updated until the View is measured or 9398 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9399 * 9400 * @param child Child to query 9401 * @return The total height of item decorations applied to the child's bottom. 9402 * @see #getDecoratedBottom(View) 9403 * @see #calculateItemDecorationsForChild(View, Rect) 9404 */ 9405 public int getBottomDecorationHeight(@NonNull View child) { 9406 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; 9407 } 9408 9409 /** 9410 * Returns the total width of item decorations applied to child's left. 9411 * <p> 9412 * Note that this value is not updated until the View is measured or 9413 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9414 * 9415 * @param child Child to query 9416 * @return The total width of item decorations applied to the child's left. 9417 * @see #getDecoratedLeft(View) 9418 * @see #calculateItemDecorationsForChild(View, Rect) 9419 */ 9420 public int getLeftDecorationWidth(@NonNull View child) { 9421 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; 9422 } 9423 9424 /** 9425 * Returns the total width of item decorations applied to child's right. 9426 * <p> 9427 * Note that this value is not updated until the View is measured or 9428 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 9429 * 9430 * @param child Child to query 9431 * @return The total width of item decorations applied to the child's right. 9432 * @see #getDecoratedRight(View) 9433 * @see #calculateItemDecorationsForChild(View, Rect) 9434 */ 9435 public int getRightDecorationWidth(@NonNull View child) { 9436 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; 9437 } 9438 9439 /** 9440 * Called when searching for a focusable view in the given direction has failed 9441 * for the current content of the RecyclerView. 9442 * 9443 * <p>This is the LayoutManager's opportunity to populate views in the given direction 9444 * to fulfill the request if it can. The LayoutManager should attach and return 9445 * the view to be focused, if a focusable view in the given direction is found. 9446 * Otherwise, if all the existing (or the newly populated views) are unfocusable, it returns 9447 * the next unfocusable view to become visible on the screen. This unfocusable view is 9448 * typically the first view that's either partially or fully out of RV's padded bounded 9449 * area in the given direction. The default implementation returns null.</p> 9450 * 9451 * @param focused The currently focused view 9452 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9453 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9454 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9455 * or 0 for not applicable 9456 * @param recycler The recycler to use for obtaining views for currently offscreen items 9457 * @param state Transient state of RecyclerView 9458 * @return The chosen view to be focused if a focusable view is found, otherwise an 9459 * unfocusable view to become visible onto the screen, else null. 9460 */ 9461 @Nullable 9462 public View onFocusSearchFailed(@NonNull View focused, int direction, 9463 @NonNull Recycler recycler, @NonNull State state) { 9464 return null; 9465 } 9466 9467 /** 9468 * This method gives a LayoutManager an opportunity to intercept the initial focus search 9469 * before the default behavior of {@link FocusFinder} is used. If this method returns 9470 * null FocusFinder will attempt to find a focusable child view. If it fails 9471 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} 9472 * will be called to give the LayoutManager an opportunity to add new views for items 9473 * that did not have attached views representing them. The LayoutManager should not add 9474 * or remove views from this method. 9475 * 9476 * @param focused The currently focused view 9477 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9478 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9479 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9480 * @return A descendant view to focus or null to fall back to default behavior. 9481 * The default implementation returns null. 9482 */ 9483 @Nullable 9484 public View onInterceptFocusSearch(@NonNull View focused, int direction) { 9485 return null; 9486 } 9487 9488 /** 9489 * Returns the scroll amount that brings the given rect in child's coordinate system within 9490 * the padded area of RecyclerView. 9491 * @param parent The parent RecyclerView. 9492 * @param child The direct child making the request. 9493 * @param rect The rectangle in the child's coordinates the child 9494 * wishes to be on the screen. 9495 * @param immediate True to forbid animated or delayed scrolling, 9496 * false otherwise 9497 * @return The array containing the scroll amount in x and y directions that brings the 9498 * given rect into RV's padded area. 9499 */ 9500 private int[] getChildRectangleOnScreenScrollAmount(RecyclerView parent, View child, 9501 Rect rect, boolean immediate) { 9502 int[] out = new int[2]; 9503 final int parentLeft = getPaddingLeft(); 9504 final int parentTop = getPaddingTop(); 9505 final int parentRight = getWidth() - getPaddingRight(); 9506 final int parentBottom = getHeight() - getPaddingBottom(); 9507 final int childLeft = child.getLeft() + rect.left - child.getScrollX(); 9508 final int childTop = child.getTop() + rect.top - child.getScrollY(); 9509 final int childRight = childLeft + rect.width(); 9510 final int childBottom = childTop + rect.height(); 9511 9512 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 9513 final int offScreenTop = Math.min(0, childTop - parentTop); 9514 final int offScreenRight = Math.max(0, childRight - parentRight); 9515 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 9516 9517 // Favor the "start" layout direction over the end when bringing one side or the other 9518 // of a large rect into view. If we decide to bring in end because start is already 9519 // visible, limit the scroll such that start won't go out of bounds. 9520 final int dx; 9521 if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) { 9522 dx = offScreenRight != 0 ? offScreenRight 9523 : Math.max(offScreenLeft, childRight - parentRight); 9524 } else { 9525 dx = offScreenLeft != 0 ? offScreenLeft 9526 : Math.min(childLeft - parentLeft, offScreenRight); 9527 } 9528 9529 // Favor bringing the top into view over the bottom. If top is already visible and 9530 // we should scroll to make bottom visible, make sure top does not go out of bounds. 9531 final int dy = offScreenTop != 0 ? offScreenTop 9532 : Math.min(childTop - parentTop, offScreenBottom); 9533 out[0] = dx; 9534 out[1] = dy; 9535 return out; 9536 } 9537 /** 9538 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 9539 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 9540 * android.graphics.Rect, boolean)} for more details. 9541 * 9542 * <p>The base implementation will attempt to perform a standard programmatic scroll 9543 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 9544 * 9545 * @param child The direct child making the request. 9546 * @param rect The rectangle in the child's coordinates the child 9547 * wishes to be on the screen. 9548 * @param immediate True to forbid animated or delayed scrolling, 9549 * false otherwise 9550 * @return Whether the group scrolled to handle the operation 9551 */ 9552 public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, 9553 @NonNull View child, @NonNull Rect rect, boolean immediate) { 9554 return requestChildRectangleOnScreen(parent, child, rect, immediate, false); 9555 } 9556 9557 /** 9558 * Requests that the given child of the RecyclerView be positioned onto the screen. This 9559 * method can be called for both unfocusable and focusable child views. For unfocusable 9560 * child views, focusedChildVisible is typically true in which case, layout manager 9561 * makes the child view visible only if the currently focused child stays in-bounds of RV. 9562 * @param parent The parent RecyclerView. 9563 * @param child The direct child making the request. 9564 * @param rect The rectangle in the child's coordinates the child 9565 * wishes to be on the screen. 9566 * @param immediate True to forbid animated or delayed scrolling, 9567 * false otherwise 9568 * @param focusedChildVisible Whether the currently focused view must stay visible. 9569 * @return Whether the group scrolled to handle the operation 9570 */ 9571 public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, 9572 @NonNull View child, @NonNull Rect rect, boolean immediate, 9573 boolean focusedChildVisible) { 9574 int[] scrollAmount = getChildRectangleOnScreenScrollAmount(parent, child, rect, 9575 immediate); 9576 int dx = scrollAmount[0]; 9577 int dy = scrollAmount[1]; 9578 if (!focusedChildVisible || isFocusedChildVisibleAfterScrolling(parent, dx, dy)) { 9579 if (dx != 0 || dy != 0) { 9580 if (immediate) { 9581 parent.scrollBy(dx, dy); 9582 } else { 9583 parent.smoothScrollBy(dx, dy); 9584 } 9585 return true; 9586 } 9587 } 9588 return false; 9589 } 9590 9591 /** 9592 * Returns whether the given child view is partially or fully visible within the padded 9593 * bounded area of RecyclerView, depending on the input parameters. 9594 * A view is partially visible if it has non-zero overlap with RV's padded bounded area. 9595 * If acceptEndPointInclusion flag is set to true, it's also considered partially 9596 * visible if it's located outside RV's bounds and it's hitting either RV's start or end 9597 * bounds. 9598 * 9599 * @param child The child view to be examined. 9600 * @param completelyVisible If true, the method returns true if and only if the child is 9601 * completely visible. If false, the method returns true if and 9602 * only if the child is only partially visible (that is it will 9603 * return false if the child is either completely visible or out 9604 * of RV's bounds). 9605 * @param acceptEndPointInclusion If the view's endpoint intersection with RV's start of end 9606 * bounds is enough to consider it partially visible, 9607 * false otherwise. 9608 * @return True if the given child is partially or fully visible, false otherwise. 9609 */ 9610 public boolean isViewPartiallyVisible(@NonNull View child, boolean completelyVisible, 9611 boolean acceptEndPointInclusion) { 9612 int boundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS 9613 | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); 9614 boolean isViewFullyVisible = mHorizontalBoundCheck.isViewWithinBoundFlags(child, 9615 boundsFlag) 9616 && mVerticalBoundCheck.isViewWithinBoundFlags(child, boundsFlag); 9617 if (completelyVisible) { 9618 return isViewFullyVisible; 9619 } else { 9620 return !isViewFullyVisible; 9621 } 9622 } 9623 9624 /** 9625 * Returns whether the currently focused child stays within RV's bounds with the given 9626 * amount of scrolling. 9627 * @param parent The parent RecyclerView. 9628 * @param dx The scrolling in x-axis direction to be performed. 9629 * @param dy The scrolling in y-axis direction to be performed. 9630 * @return {@code false} if the focused child is not at least partially visible after 9631 * scrolling or no focused child exists, {@code true} otherwise. 9632 */ 9633 private boolean isFocusedChildVisibleAfterScrolling(RecyclerView parent, int dx, int dy) { 9634 final View focusedChild = parent.getFocusedChild(); 9635 if (focusedChild == null) { 9636 return false; 9637 } 9638 final int parentLeft = getPaddingLeft(); 9639 final int parentTop = getPaddingTop(); 9640 final int parentRight = getWidth() - getPaddingRight(); 9641 final int parentBottom = getHeight() - getPaddingBottom(); 9642 final Rect bounds = mRecyclerView.mTempRect; 9643 getDecoratedBoundsWithMargins(focusedChild, bounds); 9644 9645 if (bounds.left - dx >= parentRight || bounds.right - dx <= parentLeft 9646 || bounds.top - dy >= parentBottom || bounds.bottom - dy <= parentTop) { 9647 return false; 9648 } 9649 return true; 9650 } 9651 9652 /** 9653 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} 9654 */ 9655 @Deprecated 9656 public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull View child, 9657 @Nullable View focused) { 9658 // eat the request if we are in the middle of a scroll or layout 9659 return isSmoothScrolling() || parent.isComputingLayout(); 9660 } 9661 9662 /** 9663 * Called when a descendant view of the RecyclerView requests focus. 9664 * 9665 * <p>A LayoutManager wishing to keep focused views aligned in a specific 9666 * portion of the view may implement that behavior in an override of this method.</p> 9667 * 9668 * <p>If the LayoutManager executes different behavior that should override the default 9669 * behavior of scrolling the focused child on screen instead of running alongside it, 9670 * this method should return true.</p> 9671 * 9672 * @param parent The RecyclerView hosting this LayoutManager 9673 * @param state Current state of RecyclerView 9674 * @param child Direct child of the RecyclerView containing the newly focused view 9675 * @param focused The newly focused view. This may be the same view as child or it may be 9676 * null 9677 * @return true if the default scroll behavior should be suppressed 9678 */ 9679 public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull State state, 9680 @NonNull View child, @Nullable View focused) { 9681 return onRequestChildFocus(parent, child, focused); 9682 } 9683 9684 /** 9685 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via 9686 * {@link RecyclerView#setAdapter(Adapter)} or 9687 * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this 9688 * opportunity to clear caches and configure state such that it can relayout appropriately 9689 * with the new data and potentially new view types. 9690 * 9691 * <p>The default implementation removes all currently attached views.</p> 9692 * 9693 * @param oldAdapter The previous adapter instance. Will be null if there was previously no 9694 * adapter. 9695 * @param newAdapter The new adapter instance. Might be null if 9696 * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}. 9697 */ 9698 public void onAdapterChanged(@Nullable Adapter oldAdapter, @Nullable Adapter newAdapter) { 9699 } 9700 9701 /** 9702 * Called to populate focusable views within the RecyclerView. 9703 * 9704 * <p>The LayoutManager implementation should return <code>true</code> if the default 9705 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 9706 * suppressed.</p> 9707 * 9708 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 9709 * to fall back to the default ViewGroup behavior.</p> 9710 * 9711 * @param recyclerView The RecyclerView hosting this LayoutManager 9712 * @param views List of output views. This method should add valid focusable views 9713 * to this list. 9714 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 9715 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 9716 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 9717 * @param focusableMode The type of focusables to be added. 9718 * 9719 * @return true to suppress the default behavior, false to add default focusables after 9720 * this method returns. 9721 * 9722 * @see #FOCUSABLES_ALL 9723 * @see #FOCUSABLES_TOUCH_MODE 9724 */ 9725 public boolean onAddFocusables(@NonNull RecyclerView recyclerView, 9726 @NonNull ArrayList<View> views, int direction, int focusableMode) { 9727 return false; 9728 } 9729 9730 /** 9731 * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or 9732 * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire 9733 * data set has changed. 9734 * 9735 * @param recyclerView 9736 */ 9737 public void onItemsChanged(@NonNull RecyclerView recyclerView) { 9738 } 9739 9740 /** 9741 * Called when items have been added to the adapter. The LayoutManager may choose to 9742 * requestLayout if the inserted items would require refreshing the currently visible set 9743 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 9744 * 9745 * @param recyclerView 9746 * @param positionStart 9747 * @param itemCount 9748 */ 9749 public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, 9750 int itemCount) { 9751 } 9752 9753 /** 9754 * Called when items have been removed from the adapter. 9755 * 9756 * @param recyclerView 9757 * @param positionStart 9758 * @param itemCount 9759 */ 9760 public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, 9761 int itemCount) { 9762 } 9763 9764 /** 9765 * Called when items have been changed in the adapter. 9766 * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)} 9767 * instead, then this callback will not be invoked. 9768 * 9769 * @param recyclerView 9770 * @param positionStart 9771 * @param itemCount 9772 */ 9773 public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, 9774 int itemCount) { 9775 } 9776 9777 /** 9778 * Called when items have been changed in the adapter and with optional payload. 9779 * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}. 9780 * 9781 * @param recyclerView 9782 * @param positionStart 9783 * @param itemCount 9784 * @param payload 9785 */ 9786 public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, 9787 int itemCount, @Nullable Object payload) { 9788 onItemsUpdated(recyclerView, positionStart, itemCount); 9789 } 9790 9791 /** 9792 * Called when an item is moved withing the adapter. 9793 * <p> 9794 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE 9795 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} 9796 * is called. 9797 * 9798 * @param recyclerView 9799 * @param from 9800 * @param to 9801 * @param itemCount 9802 */ 9803 public void onItemsMoved(@NonNull RecyclerView recyclerView, int from, int to, 9804 int itemCount) { 9805 9806 } 9807 9808 9809 /** 9810 * <p>Override this method if you want to support scroll bars.</p> 9811 * 9812 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> 9813 * 9814 * <p>Default implementation returns 0.</p> 9815 * 9816 * @param state Current state of RecyclerView 9817 * @return The horizontal extent of the scrollbar's thumb 9818 * @see RecyclerView#computeHorizontalScrollExtent() 9819 */ 9820 public int computeHorizontalScrollExtent(@NonNull State state) { 9821 return 0; 9822 } 9823 9824 /** 9825 * <p>Override this method if you want to support scroll bars.</p> 9826 * 9827 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> 9828 * 9829 * <p>Default implementation returns 0.</p> 9830 * 9831 * @param state Current State of RecyclerView where you can find total item count 9832 * @return The horizontal offset of the scrollbar's thumb 9833 * @see RecyclerView#computeHorizontalScrollOffset() 9834 */ 9835 public int computeHorizontalScrollOffset(@NonNull State state) { 9836 return 0; 9837 } 9838 9839 /** 9840 * <p>Override this method if you want to support scroll bars.</p> 9841 * 9842 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> 9843 * 9844 * <p>Default implementation returns 0.</p> 9845 * 9846 * @param state Current State of RecyclerView where you can find total item count 9847 * @return The total horizontal range represented by the vertical scrollbar 9848 * @see RecyclerView#computeHorizontalScrollRange() 9849 */ 9850 public int computeHorizontalScrollRange(@NonNull State state) { 9851 return 0; 9852 } 9853 9854 /** 9855 * <p>Override this method if you want to support scroll bars.</p> 9856 * 9857 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> 9858 * 9859 * <p>Default implementation returns 0.</p> 9860 * 9861 * @param state Current state of RecyclerView 9862 * @return The vertical extent of the scrollbar's thumb 9863 * @see RecyclerView#computeVerticalScrollExtent() 9864 */ 9865 public int computeVerticalScrollExtent(@NonNull State state) { 9866 return 0; 9867 } 9868 9869 /** 9870 * <p>Override this method if you want to support scroll bars.</p> 9871 * 9872 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> 9873 * 9874 * <p>Default implementation returns 0.</p> 9875 * 9876 * @param state Current State of RecyclerView where you can find total item count 9877 * @return The vertical offset of the scrollbar's thumb 9878 * @see RecyclerView#computeVerticalScrollOffset() 9879 */ 9880 public int computeVerticalScrollOffset(@NonNull State state) { 9881 return 0; 9882 } 9883 9884 /** 9885 * <p>Override this method if you want to support scroll bars.</p> 9886 * 9887 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> 9888 * 9889 * <p>Default implementation returns 0.</p> 9890 * 9891 * @param state Current State of RecyclerView where you can find total item count 9892 * @return The total vertical range represented by the vertical scrollbar 9893 * @see RecyclerView#computeVerticalScrollRange() 9894 */ 9895 public int computeVerticalScrollRange(@NonNull State state) { 9896 return 0; 9897 } 9898 9899 /** 9900 * Measure the attached RecyclerView. Implementations must call 9901 * {@link #setMeasuredDimension(int, int)} before returning. 9902 * <p> 9903 * It is strongly advised to use the AutoMeasure mechanism by overriding 9904 * {@link #isAutoMeasureEnabled()} to return true as AutoMeasure handles all the standard 9905 * measure cases including when the RecyclerView's layout_width or layout_height have been 9906 * set to wrap_content. If {@link #isAutoMeasureEnabled()} is overridden to return true, 9907 * this method should not be overridden. 9908 * <p> 9909 * The default implementation will handle EXACTLY measurements and respect 9910 * the minimum width and height properties of the host RecyclerView if measured 9911 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView 9912 * will consume all available space. 9913 * 9914 * @param recycler Recycler 9915 * @param state Transient state of RecyclerView 9916 * @param widthSpec Width {@link android.view.View.MeasureSpec} 9917 * @param heightSpec Height {@link android.view.View.MeasureSpec} 9918 * 9919 * @see #isAutoMeasureEnabled() 9920 * @see #setMeasuredDimension(int, int) 9921 */ 9922 public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, 9923 int heightSpec) { 9924 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 9925 } 9926 9927 /** 9928 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the 9929 * host RecyclerView. 9930 * 9931 * @param widthSize Measured width 9932 * @param heightSize Measured height 9933 */ 9934 public void setMeasuredDimension(int widthSize, int heightSize) { 9935 mRecyclerView.setMeasuredDimension(widthSize, heightSize); 9936 } 9937 9938 /** 9939 * @return The host RecyclerView's {@link View#getMinimumWidth()} 9940 */ 9941 @Px 9942 public int getMinimumWidth() { 9943 return ViewCompat.getMinimumWidth(mRecyclerView); 9944 } 9945 9946 /** 9947 * @return The host RecyclerView's {@link View#getMinimumHeight()} 9948 */ 9949 @Px 9950 public int getMinimumHeight() { 9951 return ViewCompat.getMinimumHeight(mRecyclerView); 9952 } 9953 /** 9954 * <p>Called when the LayoutManager should save its state. This is a good time to save your 9955 * scroll position, configuration and anything else that may be required to restore the same 9956 * layout state if the LayoutManager is recreated.</p> 9957 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and 9958 * restore. This will let you share information between your LayoutManagers but it is also 9959 * your responsibility to make sure they use the same parcelable class.</p> 9960 * 9961 * @return Necessary information for LayoutManager to be able to restore its state 9962 */ 9963 @Nullable 9964 public Parcelable onSaveInstanceState() { 9965 return null; 9966 } 9967 9968 9969 public void onRestoreInstanceState(Parcelable state) { 9970 9971 } 9972 9973 void stopSmoothScroller() { 9974 if (mSmoothScroller != null) { 9975 mSmoothScroller.stop(); 9976 } 9977 } 9978 9979 private void onSmoothScrollerStopped(SmoothScroller smoothScroller) { 9980 if (mSmoothScroller == smoothScroller) { 9981 mSmoothScroller = null; 9982 } 9983 } 9984 9985 /** 9986 * RecyclerView calls this method to notify LayoutManager that scroll state has changed. 9987 * 9988 * @param state The new scroll state for RecyclerView 9989 */ 9990 public void onScrollStateChanged(int state) { 9991 } 9992 9993 /** 9994 * Removes all views and recycles them using the given recycler. 9995 * <p> 9996 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. 9997 * <p> 9998 * If a View is marked as "ignored", it is not removed nor recycled. 9999 * 10000 * @param recycler Recycler to use to recycle children 10001 * @see #removeAndRecycleView(View, Recycler) 10002 * @see #removeAndRecycleViewAt(int, Recycler) 10003 * @see #ignoreView(View) 10004 */ 10005 public void removeAndRecycleAllViews(@NonNull Recycler recycler) { 10006 for (int i = getChildCount() - 1; i >= 0; i--) { 10007 final View view = getChildAt(i); 10008 if (!getChildViewHolderInt(view).shouldIgnore()) { 10009 removeAndRecycleViewAt(i, recycler); 10010 } 10011 } 10012 } 10013 10014 // called by accessibility delegate 10015 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 10016 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info); 10017 } 10018 10019 /** 10020 * Called by the AccessibilityDelegate when the information about the current layout should 10021 * be populated. 10022 * <p> 10023 * Default implementation adds a {@link 10024 * androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. 10025 * <p> 10026 * You should override 10027 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 10028 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 10029 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and 10030 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for 10031 * more accurate accessibility information. 10032 * 10033 * @param recycler The Recycler that can be used to convert view positions into adapter 10034 * positions 10035 * @param state The current state of RecyclerView 10036 * @param info The info that should be filled by the LayoutManager 10037 * @see View#onInitializeAccessibilityNodeInfo( 10038 *android.view.accessibility.AccessibilityNodeInfo) 10039 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10040 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10041 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) 10042 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) 10043 */ 10044 public void onInitializeAccessibilityNodeInfo(@NonNull Recycler recycler, 10045 @NonNull State state, @NonNull AccessibilityNodeInfoCompat info) { 10046 if (mRecyclerView.canScrollVertically(-1) || mRecyclerView.canScrollHorizontally(-1)) { 10047 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 10048 info.setScrollable(true); 10049 } 10050 if (mRecyclerView.canScrollVertically(1) || mRecyclerView.canScrollHorizontally(1)) { 10051 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 10052 info.setScrollable(true); 10053 } 10054 final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo = 10055 AccessibilityNodeInfoCompat.CollectionInfoCompat 10056 .obtain(getRowCountForAccessibility(recycler, state), 10057 getColumnCountForAccessibility(recycler, state), 10058 isLayoutHierarchical(recycler, state), 10059 getSelectionModeForAccessibility(recycler, state)); 10060 info.setCollectionInfo(collectionInfo); 10061 } 10062 10063 // called by accessibility delegate 10064 public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) { 10065 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); 10066 } 10067 10068 /** 10069 * Called by the accessibility delegate to initialize an accessibility event. 10070 * <p> 10071 * Default implementation adds item count and scroll information to the event. 10072 * 10073 * @param recycler The Recycler that can be used to convert view positions into adapter 10074 * positions 10075 * @param state The current state of RecyclerView 10076 * @param event The event instance to initialize 10077 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) 10078 */ 10079 public void onInitializeAccessibilityEvent(@NonNull Recycler recycler, @NonNull State state, 10080 @NonNull AccessibilityEvent event) { 10081 if (mRecyclerView == null || event == null) { 10082 return; 10083 } 10084 event.setScrollable(mRecyclerView.canScrollVertically(1) 10085 || mRecyclerView.canScrollVertically(-1) 10086 || mRecyclerView.canScrollHorizontally(-1) 10087 || mRecyclerView.canScrollHorizontally(1)); 10088 10089 if (mRecyclerView.mAdapter != null) { 10090 event.setItemCount(mRecyclerView.mAdapter.getItemCount()); 10091 } 10092 } 10093 10094 // called by accessibility delegate 10095 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { 10096 final ViewHolder vh = getChildViewHolderInt(host); 10097 // avoid trying to create accessibility node info for removed children 10098 if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) { 10099 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, 10100 mRecyclerView.mState, host, info); 10101 } 10102 } 10103 10104 /** 10105 * Called by the AccessibilityDelegate when the accessibility information for a specific 10106 * item should be populated. 10107 * <p> 10108 * Default implementation adds basic positioning information about the item. 10109 * 10110 * @param recycler The Recycler that can be used to convert view positions into adapter 10111 * positions 10112 * @param state The current state of RecyclerView 10113 * @param host The child for which accessibility node info should be populated 10114 * @param info The info to fill out about the item 10115 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, 10116 * android.view.accessibility.AccessibilityNodeInfo) 10117 */ 10118 public void onInitializeAccessibilityNodeInfoForItem(@NonNull Recycler recycler, 10119 @NonNull State state, @NonNull View host, 10120 @NonNull AccessibilityNodeInfoCompat info) { 10121 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; 10122 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; 10123 final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo = 10124 AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, 10125 columnIndexGuess, 1, false, false); 10126 info.setCollectionItemInfo(itemInfo); 10127 } 10128 10129 /** 10130 * A LayoutManager can call this method to force RecyclerView to run simple animations in 10131 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data 10132 * change). 10133 * <p> 10134 * Note that, calling this method will not guarantee that RecyclerView will run animations 10135 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will 10136 * not run any animations but will still clear this flag after the layout is complete. 10137 * 10138 */ 10139 public void requestSimpleAnimationsInNextLayout() { 10140 mRequestedSimpleAnimations = true; 10141 } 10142 10143 /** 10144 * Returns the selection mode for accessibility. Should be 10145 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, 10146 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or 10147 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. 10148 * <p> 10149 * Default implementation returns 10150 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 10151 * 10152 * @param recycler The Recycler that can be used to convert view positions into adapter 10153 * positions 10154 * @param state The current state of RecyclerView 10155 * @return Selection mode for accessibility. Default implementation returns 10156 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 10157 */ 10158 public int getSelectionModeForAccessibility(@NonNull Recycler recycler, 10159 @NonNull State state) { 10160 return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; 10161 } 10162 10163 /** 10164 * Returns the number of rows for accessibility. 10165 * <p> 10166 * Default implementation returns the number of items in the adapter if LayoutManager 10167 * supports vertical scrolling or 1 if LayoutManager does not support vertical 10168 * scrolling. 10169 * 10170 * @param recycler The Recycler that can be used to convert view positions into adapter 10171 * positions 10172 * @param state The current state of RecyclerView 10173 * @return The number of rows in LayoutManager for accessibility. 10174 */ 10175 public int getRowCountForAccessibility(@NonNull Recycler recycler, @NonNull State state) { 10176 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 10177 return 1; 10178 } 10179 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; 10180 } 10181 10182 /** 10183 * Returns the number of columns for accessibility. 10184 * <p> 10185 * Default implementation returns the number of items in the adapter if LayoutManager 10186 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal 10187 * scrolling. 10188 * 10189 * @param recycler The Recycler that can be used to convert view positions into adapter 10190 * positions 10191 * @param state The current state of RecyclerView 10192 * @return The number of rows in LayoutManager for accessibility. 10193 */ 10194 public int getColumnCountForAccessibility(@NonNull Recycler recycler, 10195 @NonNull State state) { 10196 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 10197 return 1; 10198 } 10199 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; 10200 } 10201 10202 /** 10203 * Returns whether layout is hierarchical or not to be used for accessibility. 10204 * <p> 10205 * Default implementation returns false. 10206 * 10207 * @param recycler The Recycler that can be used to convert view positions into adapter 10208 * positions 10209 * @param state The current state of RecyclerView 10210 * @return True if layout is hierarchical. 10211 */ 10212 public boolean isLayoutHierarchical(@NonNull Recycler recycler, @NonNull State state) { 10213 return false; 10214 } 10215 10216 // called by accessibility delegate 10217 boolean performAccessibilityAction(int action, @Nullable Bundle args) { 10218 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, 10219 action, args); 10220 } 10221 10222 /** 10223 * Called by AccessibilityDelegate when an action is requested from the RecyclerView. 10224 * 10225 * @param recycler The Recycler that can be used to convert view positions into adapter 10226 * positions 10227 * @param state The current state of RecyclerView 10228 * @param action The action to perform 10229 * @param args Optional action arguments 10230 * @see View#performAccessibilityAction(int, android.os.Bundle) 10231 */ 10232 public boolean performAccessibilityAction(@NonNull Recycler recycler, @NonNull State state, 10233 int action, @Nullable Bundle args) { 10234 if (mRecyclerView == null) { 10235 return false; 10236 } 10237 int vScroll = 0, hScroll = 0; 10238 switch (action) { 10239 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 10240 if (mRecyclerView.canScrollVertically(-1)) { 10241 vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); 10242 } 10243 if (mRecyclerView.canScrollHorizontally(-1)) { 10244 hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); 10245 } 10246 break; 10247 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 10248 if (mRecyclerView.canScrollVertically(1)) { 10249 vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); 10250 } 10251 if (mRecyclerView.canScrollHorizontally(1)) { 10252 hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); 10253 } 10254 break; 10255 } 10256 if (vScroll == 0 && hScroll == 0) { 10257 return false; 10258 } 10259 mRecyclerView.smoothScrollBy(hScroll, vScroll); 10260 return true; 10261 } 10262 10263 // called by accessibility delegate 10264 boolean performAccessibilityActionForItem(@NonNull View view, int action, 10265 @Nullable Bundle args) { 10266 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, 10267 view, action, args); 10268 } 10269 10270 /** 10271 * Called by AccessibilityDelegate when an accessibility action is requested on one of the 10272 * children of LayoutManager. 10273 * <p> 10274 * Default implementation does not do anything. 10275 * 10276 * @param recycler The Recycler that can be used to convert view positions into adapter 10277 * positions 10278 * @param state The current state of RecyclerView 10279 * @param view The child view on which the action is performed 10280 * @param action The action to perform 10281 * @param args Optional action arguments 10282 * @return true if action is handled 10283 * @see View#performAccessibilityAction(int, android.os.Bundle) 10284 */ 10285 public boolean performAccessibilityActionForItem(@NonNull Recycler recycler, 10286 @NonNull State state, @NonNull View view, int action, @Nullable Bundle args) { 10287 return false; 10288 } 10289 10290 /** 10291 * Parse the xml attributes to get the most common properties used by layout managers. 10292 * 10293 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation 10294 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount 10295 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout 10296 * @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd 10297 * 10298 * @return an object containing the properties as specified in the attrs. 10299 */ 10300 public static Properties getProperties(@NonNull Context context, 10301 @Nullable AttributeSet attrs, 10302 int defStyleAttr, int defStyleRes) { 10303 Properties properties = new Properties(); 10304 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 10305 defStyleAttr, defStyleRes); 10306 properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation, 10307 DEFAULT_ORIENTATION); 10308 properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1); 10309 properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false); 10310 properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false); 10311 a.recycle(); 10312 return properties; 10313 } 10314 10315 void setExactMeasureSpecsFrom(RecyclerView recyclerView) { 10316 setMeasureSpecs( 10317 MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), 10318 MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) 10319 ); 10320 } 10321 10322 /** 10323 * Internal API to allow LayoutManagers to be measured twice. 10324 * <p> 10325 * This is not public because LayoutManagers should be able to handle their layouts in one 10326 * pass but it is very convenient to make existing LayoutManagers support wrapping content 10327 * when both orientations are undefined. 10328 * <p> 10329 * This API will be removed after default LayoutManagers properly implement wrap content in 10330 * non-scroll orientation. 10331 */ 10332 boolean shouldMeasureTwice() { 10333 return false; 10334 } 10335 10336 boolean hasFlexibleChildInBothOrientations() { 10337 final int childCount = getChildCount(); 10338 for (int i = 0; i < childCount; i++) { 10339 final View child = getChildAt(i); 10340 final ViewGroup.LayoutParams lp = child.getLayoutParams(); 10341 if (lp.width < 0 && lp.height < 0) { 10342 return true; 10343 } 10344 } 10345 return false; 10346 } 10347 10348 /** 10349 * Some general properties that a LayoutManager may want to use. 10350 */ 10351 public static class Properties { 10352 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation */ 10353 public int orientation; 10354 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount */ 10355 public int spanCount; 10356 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout */ 10357 public boolean reverseLayout; 10358 /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd */ 10359 public boolean stackFromEnd; 10360 } 10361 } 10362 10363 /** 10364 * An ItemDecoration allows the application to add a special drawing and layout offset 10365 * to specific item views from the adapter's data set. This can be useful for drawing dividers 10366 * between items, highlights, visual grouping boundaries and more. 10367 * 10368 * <p>All ItemDecorations are drawn in the order they were added, before the item 10369 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} 10370 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, 10371 * RecyclerView.State)}.</p> 10372 */ 10373 public abstract static class ItemDecoration { 10374 /** 10375 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 10376 * Any content drawn by this method will be drawn before the item views are drawn, 10377 * and will thus appear underneath the views. 10378 * 10379 * @param c Canvas to draw into 10380 * @param parent RecyclerView this ItemDecoration is drawing into 10381 * @param state The current state of RecyclerView 10382 */ 10383 public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) { 10384 onDraw(c, parent); 10385 } 10386 10387 /** 10388 * @deprecated 10389 * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} 10390 */ 10391 @Deprecated 10392 public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) { 10393 } 10394 10395 /** 10396 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 10397 * Any content drawn by this method will be drawn after the item views are drawn 10398 * and will thus appear over the views. 10399 * 10400 * @param c Canvas to draw into 10401 * @param parent RecyclerView this ItemDecoration is drawing into 10402 * @param state The current state of RecyclerView. 10403 */ 10404 public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, 10405 @NonNull State state) { 10406 onDrawOver(c, parent); 10407 } 10408 10409 /** 10410 * @deprecated 10411 * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} 10412 */ 10413 @Deprecated 10414 public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) { 10415 } 10416 10417 10418 /** 10419 * @deprecated 10420 * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} 10421 */ 10422 @Deprecated 10423 public void getItemOffsets(@NonNull Rect outRect, int itemPosition, 10424 @NonNull RecyclerView parent) { 10425 outRect.set(0, 0, 0, 0); 10426 } 10427 10428 /** 10429 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 10430 * the number of pixels that the item view should be inset by, similar to padding or margin. 10431 * The default implementation sets the bounds of outRect to 0 and returns. 10432 * 10433 * <p> 10434 * If this ItemDecoration does not affect the positioning of item views, it should set 10435 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 10436 * before returning. 10437 * 10438 * <p> 10439 * If you need to access Adapter for additional data, you can call 10440 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the 10441 * View. 10442 * 10443 * @param outRect Rect to receive the output. 10444 * @param view The child view to decorate 10445 * @param parent RecyclerView this ItemDecoration is decorating 10446 * @param state The current state of RecyclerView. 10447 */ 10448 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 10449 @NonNull RecyclerView parent, @NonNull State state) { 10450 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), 10451 parent); 10452 } 10453 } 10454 10455 /** 10456 * An OnItemTouchListener allows the application to intercept touch events in progress at the 10457 * view hierarchy level of the RecyclerView before those touch events are considered for 10458 * RecyclerView's own scrolling behavior. 10459 * 10460 * <p>This can be useful for applications that wish to implement various forms of gestural 10461 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 10462 * a touch interaction already in progress even if the RecyclerView is already handling that 10463 * gesture stream itself for the purposes of scrolling.</p> 10464 * 10465 * @see SimpleOnItemTouchListener 10466 */ 10467 public interface OnItemTouchListener { 10468 /** 10469 * Silently observe and/or take over touch events sent to the RecyclerView 10470 * before they are handled by either the RecyclerView itself or its child views. 10471 * 10472 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 10473 * in the order in which each listener was added, before any other touch processing 10474 * by the RecyclerView itself or child views occurs.</p> 10475 * 10476 * @param e MotionEvent describing the touch event. All coordinates are in 10477 * the RecyclerView's coordinate system. 10478 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 10479 * to continue with the current behavior and continue observing future events in 10480 * the gesture. 10481 */ 10482 boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 10483 10484 /** 10485 * Process a touch event as part of a gesture that was claimed by returning true from 10486 * a previous call to {@link #onInterceptTouchEvent}. 10487 * 10488 * @param e MotionEvent describing the touch event. All coordinates are in 10489 * the RecyclerView's coordinate system. 10490 */ 10491 void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 10492 10493 /** 10494 * Called when a child of RecyclerView does not want RecyclerView and its ancestors to 10495 * intercept touch events with 10496 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. 10497 * 10498 * @param disallowIntercept True if the child does not want the parent to 10499 * intercept touch events. 10500 * @see ViewParent#requestDisallowInterceptTouchEvent(boolean) 10501 */ 10502 void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); 10503 } 10504 10505 /** 10506 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies 10507 * and default return values. 10508 * <p> 10509 * You may prefer to extend this class if you don't need to override all methods. Another 10510 * benefit of using this class is future compatibility. As the interface may change, we'll 10511 * always provide a default implementation on this class so that your code won't break when 10512 * you update to a new version of the support library. 10513 */ 10514 public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener { 10515 @Override 10516 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 10517 return false; 10518 } 10519 10520 @Override 10521 public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 10522 } 10523 10524 @Override 10525 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 10526 } 10527 } 10528 10529 10530 /** 10531 * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event 10532 * has occurred on that RecyclerView. 10533 * <p> 10534 * @see RecyclerView#addOnScrollListener(OnScrollListener) 10535 * @see RecyclerView#clearOnChildAttachStateChangeListeners() 10536 * 10537 */ 10538 public abstract static class OnScrollListener { 10539 /** 10540 * Callback method to be invoked when RecyclerView's scroll state changes. 10541 * 10542 * @param recyclerView The RecyclerView whose scroll state has changed. 10543 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, 10544 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 10545 */ 10546 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){} 10547 10548 /** 10549 * Callback method to be invoked when the RecyclerView has been scrolled. This will be 10550 * called after the scroll has completed. 10551 * <p> 10552 * This callback will also be called if visible item range changes after a layout 10553 * calculation. In that case, dx and dy will be 0. 10554 * 10555 * @param recyclerView The RecyclerView which scrolled. 10556 * @param dx The amount of horizontal scroll. 10557 * @param dy The amount of vertical scroll. 10558 */ 10559 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){} 10560 } 10561 10562 /** 10563 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 10564 * a view is recycled. 10565 * 10566 * @see RecyclerView#setRecyclerListener(RecyclerListener) 10567 */ 10568 public interface RecyclerListener { 10569 10570 /** 10571 * This method is called whenever the view in the ViewHolder is recycled. 10572 * 10573 * RecyclerView calls this method right before clearing ViewHolder's internal data and 10574 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 10575 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get 10576 * its adapter position. 10577 * 10578 * @param holder The ViewHolder containing the view that was recycled 10579 */ 10580 void onViewRecycled(@NonNull ViewHolder holder); 10581 } 10582 10583 /** 10584 * A Listener interface that can be attached to a RecylcerView to get notified 10585 * whenever a ViewHolder is attached to or detached from RecyclerView. 10586 */ 10587 public interface OnChildAttachStateChangeListener { 10588 10589 /** 10590 * Called when a view is attached to the RecyclerView. 10591 * 10592 * @param view The View which is attached to the RecyclerView 10593 */ 10594 void onChildViewAttachedToWindow(@NonNull View view); 10595 10596 /** 10597 * Called when a view is detached from RecyclerView. 10598 * 10599 * @param view The View which is being detached from the RecyclerView 10600 */ 10601 void onChildViewDetachedFromWindow(@NonNull View view); 10602 } 10603 10604 /** 10605 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 10606 * 10607 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 10608 * potentially expensive {@link View#findViewById(int)} results.</p> 10609 * 10610 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 10611 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 10612 * their own custom ViewHolder implementations to store data that makes binding view contents 10613 * easier. Implementations should assume that individual item views will hold strong references 10614 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 10615 * strong references to extra off-screen item views for caching purposes</p> 10616 */ 10617 public abstract static class ViewHolder { 10618 @NonNull 10619 public final View itemView; 10620 WeakReference<RecyclerView> mNestedRecyclerView; 10621 int mPosition = NO_POSITION; 10622 int mOldPosition = NO_POSITION; 10623 long mItemId = NO_ID; 10624 int mItemViewType = INVALID_TYPE; 10625 int mPreLayoutPosition = NO_POSITION; 10626 10627 // The item that this holder is shadowing during an item change event/animation 10628 ViewHolder mShadowedHolder = null; 10629 // The item that is shadowing this holder during an item change event/animation 10630 ViewHolder mShadowingHolder = null; 10631 10632 /** 10633 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 10634 * are all valid. 10635 */ 10636 static final int FLAG_BOUND = 1 << 0; 10637 10638 /** 10639 * The data this ViewHolder's view reflects is stale and needs to be rebound 10640 * by the adapter. mPosition and mItemId are consistent. 10641 */ 10642 static final int FLAG_UPDATE = 1 << 1; 10643 10644 /** 10645 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 10646 * are not to be trusted and may no longer match the item view type. 10647 * This ViewHolder must be fully rebound to different data. 10648 */ 10649 static final int FLAG_INVALID = 1 << 2; 10650 10651 /** 10652 * This ViewHolder points at data that represents an item previously removed from the 10653 * data set. Its view may still be used for things like outgoing animations. 10654 */ 10655 static final int FLAG_REMOVED = 1 << 3; 10656 10657 /** 10658 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 10659 * and is intended to keep views around during animations. 10660 */ 10661 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 10662 10663 /** 10664 * This ViewHolder is returned from scrap which means we are expecting an addView call 10665 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until 10666 * the end of the layout pass and then recycled by RecyclerView if it is not added back to 10667 * the RecyclerView. 10668 */ 10669 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; 10670 10671 /** 10672 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove 10673 * it unless LayoutManager is replaced. 10674 * It is still fully visible to the LayoutManager. 10675 */ 10676 static final int FLAG_IGNORE = 1 << 7; 10677 10678 /** 10679 * When the View is detached form the parent, we set this flag so that we can take correct 10680 * action when we need to remove it or add it back. 10681 */ 10682 static final int FLAG_TMP_DETACHED = 1 << 8; 10683 10684 /** 10685 * Set when we can no longer determine the adapter position of this ViewHolder until it is 10686 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is 10687 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon 10688 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is 10689 * re-calculated. 10690 */ 10691 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; 10692 10693 /** 10694 * Set when a addChangePayload(null) is called 10695 */ 10696 static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; 10697 10698 /** 10699 * Used by ItemAnimator when a ViewHolder's position changes 10700 */ 10701 static final int FLAG_MOVED = 1 << 11; 10702 10703 /** 10704 * Used by ItemAnimator when a ViewHolder appears in pre-layout 10705 */ 10706 static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; 10707 10708 static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; 10709 10710 /** 10711 * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from 10712 * hidden list (as if it was scrap) without being recycled in between. 10713 * 10714 * When a ViewHolder is hidden, there are 2 paths it can be re-used: 10715 * a) Animation ends, view is recycled and used from the recycle pool. 10716 * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. 10717 * 10718 * This flag is used to represent "case b" where the ViewHolder is reused without being 10719 * recycled (thus "bounced" from the hidden list). This state requires special handling 10720 * because the ViewHolder must be added to pre layout maps for animations as if it was 10721 * already there. 10722 */ 10723 static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; 10724 10725 /** 10726 * Flags that RecyclerView assigned {@link RecyclerViewAccessibilityDelegate 10727 * #getItemDelegate()} in onBindView when app does not provide a delegate. 10728 */ 10729 static final int FLAG_SET_A11Y_ITEM_DELEGATE = 1 << 14; 10730 10731 private int mFlags; 10732 10733 private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST; 10734 10735 List<Object> mPayloads = null; 10736 List<Object> mUnmodifiedPayloads = null; 10737 10738 private int mIsRecyclableCount = 0; 10739 10740 // If non-null, view is currently considered scrap and may be reused for other data by the 10741 // scrap container. 10742 private Recycler mScrapContainer = null; 10743 // Keeps whether this ViewHolder lives in Change scrap or Attached scrap 10744 private boolean mInChangeScrap = false; 10745 10746 // Saves isImportantForAccessibility value for the view item while it's in hidden state and 10747 // marked as unimportant for accessibility. 10748 private int mWasImportantForAccessibilityBeforeHidden = 10749 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 10750 // set if we defer the accessibility state change of the view holder 10751 @VisibleForTesting 10752 int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 10753 10754 /** 10755 * Is set when VH is bound from the adapter and cleaned right before it is sent to 10756 * {@link RecycledViewPool}. 10757 */ 10758 RecyclerView mOwnerRecyclerView; 10759 10760 public ViewHolder(@NonNull View itemView) { 10761 if (itemView == null) { 10762 throw new IllegalArgumentException("itemView may not be null"); 10763 } 10764 this.itemView = itemView; 10765 } 10766 10767 void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { 10768 addFlags(ViewHolder.FLAG_REMOVED); 10769 offsetPosition(offset, applyToPreLayout); 10770 mPosition = mNewPosition; 10771 } 10772 10773 void offsetPosition(int offset, boolean applyToPreLayout) { 10774 if (mOldPosition == NO_POSITION) { 10775 mOldPosition = mPosition; 10776 } 10777 if (mPreLayoutPosition == NO_POSITION) { 10778 mPreLayoutPosition = mPosition; 10779 } 10780 if (applyToPreLayout) { 10781 mPreLayoutPosition += offset; 10782 } 10783 mPosition += offset; 10784 if (itemView.getLayoutParams() != null) { 10785 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; 10786 } 10787 } 10788 10789 void clearOldPosition() { 10790 mOldPosition = NO_POSITION; 10791 mPreLayoutPosition = NO_POSITION; 10792 } 10793 10794 void saveOldPosition() { 10795 if (mOldPosition == NO_POSITION) { 10796 mOldPosition = mPosition; 10797 } 10798 } 10799 10800 boolean shouldIgnore() { 10801 return (mFlags & FLAG_IGNORE) != 0; 10802 } 10803 10804 /** 10805 * @deprecated This method is deprecated because its meaning is ambiguous due to the async 10806 * handling of adapter updates. Please use {@link #getLayoutPosition()} or 10807 * {@link #getAdapterPosition()} depending on your use case. 10808 * 10809 * @see #getLayoutPosition() 10810 * @see #getAdapterPosition() 10811 */ 10812 @Deprecated 10813 public final int getPosition() { 10814 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 10815 } 10816 10817 /** 10818 * Returns the position of the ViewHolder in terms of the latest layout pass. 10819 * <p> 10820 * This position is mostly used by RecyclerView components to be consistent while 10821 * RecyclerView lazily processes adapter updates. 10822 * <p> 10823 * For performance and animation reasons, RecyclerView batches all adapter updates until the 10824 * next layout pass. This may cause mismatches between the Adapter position of the item and 10825 * the position it had in the latest layout calculations. 10826 * <p> 10827 * LayoutManagers should always call this method while doing calculations based on item 10828 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, 10829 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position 10830 * of the item. 10831 * <p> 10832 * If LayoutManager needs to call an external method that requires the adapter position of 10833 * the item, it can use {@link #getAdapterPosition()} or 10834 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. 10835 * 10836 * @return Returns the adapter position of the ViewHolder in the latest layout pass. 10837 * @see #getAdapterPosition() 10838 */ 10839 public final int getLayoutPosition() { 10840 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 10841 } 10842 10843 /** 10844 * Returns the Adapter position of the item represented by this ViewHolder. 10845 * <p> 10846 * Note that this might be different than the {@link #getLayoutPosition()} if there are 10847 * pending adapter updates but a new layout pass has not happened yet. 10848 * <p> 10849 * RecyclerView does not handle any adapter updates until the next layout traversal. This 10850 * may create temporary inconsistencies between what user sees on the screen and what 10851 * adapter contents have. This inconsistency is not important since it will be less than 10852 * 16ms but it might be a problem if you want to use ViewHolder position to access the 10853 * adapter. Sometimes, you may need to get the exact adapter position to do 10854 * some actions in response to user events. In that case, you should use this method which 10855 * will calculate the Adapter position of the ViewHolder. 10856 * <p> 10857 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 10858 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 10859 * 10860 * @return The adapter position of the item if it still exists in the adapter. 10861 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 10862 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 10863 * layout pass or the ViewHolder has already been recycled. 10864 */ 10865 public final int getAdapterPosition() { 10866 if (mOwnerRecyclerView == null) { 10867 return NO_POSITION; 10868 } 10869 return mOwnerRecyclerView.getAdapterPositionFor(this); 10870 } 10871 10872 /** 10873 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders 10874 * to perform animations. 10875 * <p> 10876 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its 10877 * adapter index in the previous layout. 10878 * 10879 * @return The previous adapter index of the Item represented by this ViewHolder or 10880 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is 10881 * complete). 10882 */ 10883 public final int getOldPosition() { 10884 return mOldPosition; 10885 } 10886 10887 /** 10888 * Returns The itemId represented by this ViewHolder. 10889 * 10890 * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} 10891 * otherwise 10892 */ 10893 public final long getItemId() { 10894 return mItemId; 10895 } 10896 10897 /** 10898 * @return The view type of this ViewHolder. 10899 */ 10900 public final int getItemViewType() { 10901 return mItemViewType; 10902 } 10903 10904 boolean isScrap() { 10905 return mScrapContainer != null; 10906 } 10907 10908 void unScrap() { 10909 mScrapContainer.unscrapView(this); 10910 } 10911 10912 boolean wasReturnedFromScrap() { 10913 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; 10914 } 10915 10916 void clearReturnedFromScrapFlag() { 10917 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; 10918 } 10919 10920 void clearTmpDetachFlag() { 10921 mFlags = mFlags & ~FLAG_TMP_DETACHED; 10922 } 10923 10924 void stopIgnoring() { 10925 mFlags = mFlags & ~FLAG_IGNORE; 10926 } 10927 10928 void setScrapContainer(Recycler recycler, boolean isChangeScrap) { 10929 mScrapContainer = recycler; 10930 mInChangeScrap = isChangeScrap; 10931 } 10932 10933 boolean isInvalid() { 10934 return (mFlags & FLAG_INVALID) != 0; 10935 } 10936 10937 boolean needsUpdate() { 10938 return (mFlags & FLAG_UPDATE) != 0; 10939 } 10940 10941 boolean isBound() { 10942 return (mFlags & FLAG_BOUND) != 0; 10943 } 10944 10945 boolean isRemoved() { 10946 return (mFlags & FLAG_REMOVED) != 0; 10947 } 10948 10949 boolean hasAnyOfTheFlags(int flags) { 10950 return (mFlags & flags) != 0; 10951 } 10952 10953 boolean isTmpDetached() { 10954 return (mFlags & FLAG_TMP_DETACHED) != 0; 10955 } 10956 10957 boolean isAdapterPositionUnknown() { 10958 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); 10959 } 10960 10961 void setFlags(int flags, int mask) { 10962 mFlags = (mFlags & ~mask) | (flags & mask); 10963 } 10964 10965 void addFlags(int flags) { 10966 mFlags |= flags; 10967 } 10968 10969 void addChangePayload(Object payload) { 10970 if (payload == null) { 10971 addFlags(FLAG_ADAPTER_FULLUPDATE); 10972 } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 10973 createPayloadsIfNeeded(); 10974 mPayloads.add(payload); 10975 } 10976 } 10977 10978 private void createPayloadsIfNeeded() { 10979 if (mPayloads == null) { 10980 mPayloads = new ArrayList<Object>(); 10981 mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); 10982 } 10983 } 10984 10985 void clearPayload() { 10986 if (mPayloads != null) { 10987 mPayloads.clear(); 10988 } 10989 mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE; 10990 } 10991 10992 List<Object> getUnmodifiedPayloads() { 10993 if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 10994 if (mPayloads == null || mPayloads.size() == 0) { 10995 // Initial state, no update being called. 10996 return FULLUPDATE_PAYLOADS; 10997 } 10998 // there are none-null payloads 10999 return mUnmodifiedPayloads; 11000 } else { 11001 // a full update has been called. 11002 return FULLUPDATE_PAYLOADS; 11003 } 11004 } 11005 11006 void resetInternal() { 11007 mFlags = 0; 11008 mPosition = NO_POSITION; 11009 mOldPosition = NO_POSITION; 11010 mItemId = NO_ID; 11011 mPreLayoutPosition = NO_POSITION; 11012 mIsRecyclableCount = 0; 11013 mShadowedHolder = null; 11014 mShadowingHolder = null; 11015 clearPayload(); 11016 mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 11017 mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 11018 clearNestedRecyclerViewIfNotNested(this); 11019 } 11020 11021 /** 11022 * Called when the child view enters the hidden state 11023 */ 11024 private void onEnteredHiddenState(RecyclerView parent) { 11025 // While the view item is in hidden state, make it invisible for the accessibility. 11026 if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) { 11027 mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState; 11028 } else { 11029 mWasImportantForAccessibilityBeforeHidden = 11030 ViewCompat.getImportantForAccessibility(itemView); 11031 } 11032 parent.setChildImportantForAccessibilityInternal(this, 11033 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 11034 } 11035 11036 /** 11037 * Called when the child view leaves the hidden state 11038 */ 11039 private void onLeftHiddenState(RecyclerView parent) { 11040 parent.setChildImportantForAccessibilityInternal(this, 11041 mWasImportantForAccessibilityBeforeHidden); 11042 mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 11043 } 11044 11045 @Override 11046 public String toString() { 11047 final StringBuilder sb = new StringBuilder("ViewHolder{" 11048 + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId 11049 + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); 11050 if (isScrap()) { 11051 sb.append(" scrap ") 11052 .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); 11053 } 11054 if (isInvalid()) sb.append(" invalid"); 11055 if (!isBound()) sb.append(" unbound"); 11056 if (needsUpdate()) sb.append(" update"); 11057 if (isRemoved()) sb.append(" removed"); 11058 if (shouldIgnore()) sb.append(" ignored"); 11059 if (isTmpDetached()) sb.append(" tmpDetached"); 11060 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); 11061 if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); 11062 11063 if (itemView.getParent() == null) sb.append(" no parent"); 11064 sb.append("}"); 11065 return sb.toString(); 11066 } 11067 11068 /** 11069 * Informs the recycler whether this item can be recycled. Views which are not 11070 * recyclable will not be reused for other items until setIsRecyclable() is 11071 * later set to true. Calls to setIsRecyclable() should always be paired (one 11072 * call to setIsRecyclabe(false) should always be matched with a later call to 11073 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally 11074 * reference-counted. 11075 * 11076 * @param recyclable Whether this item is available to be recycled. Default value 11077 * is true. 11078 * 11079 * @see #isRecyclable() 11080 */ 11081 public final void setIsRecyclable(boolean recyclable) { 11082 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; 11083 if (mIsRecyclableCount < 0) { 11084 mIsRecyclableCount = 0; 11085 if (DEBUG) { 11086 throw new RuntimeException("isRecyclable decremented below 0: " 11087 + "unmatched pair of setIsRecyable() calls for " + this); 11088 } 11089 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " 11090 + "unmatched pair of setIsRecyable() calls for " + this); 11091 } else if (!recyclable && mIsRecyclableCount == 1) { 11092 mFlags |= FLAG_NOT_RECYCLABLE; 11093 } else if (recyclable && mIsRecyclableCount == 0) { 11094 mFlags &= ~FLAG_NOT_RECYCLABLE; 11095 } 11096 if (DEBUG) { 11097 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); 11098 } 11099 } 11100 11101 /** 11102 * @return true if this item is available to be recycled, false otherwise. 11103 * 11104 * @see #setIsRecyclable(boolean) 11105 */ 11106 public final boolean isRecyclable() { 11107 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 11108 && !ViewCompat.hasTransientState(itemView); 11109 } 11110 11111 /** 11112 * Returns whether we have animations referring to this view holder or not. 11113 * This is similar to isRecyclable flag but does not check transient state. 11114 */ 11115 private boolean shouldBeKeptAsChild() { 11116 return (mFlags & FLAG_NOT_RECYCLABLE) != 0; 11117 } 11118 11119 /** 11120 * @return True if ViewHolder is not referenced by RecyclerView animations but has 11121 * transient state which will prevent it from being recycled. 11122 */ 11123 private boolean doesTransientStatePreventRecycling() { 11124 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); 11125 } 11126 11127 boolean isUpdated() { 11128 return (mFlags & FLAG_UPDATE) != 0; 11129 } 11130 } 11131 11132 /** 11133 * This method is here so that we can control the important for a11y changes and test it. 11134 */ 11135 @VisibleForTesting 11136 boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, 11137 int importantForAccessibility) { 11138 if (isComputingLayout()) { 11139 viewHolder.mPendingAccessibilityState = importantForAccessibility; 11140 mPendingAccessibilityImportanceChange.add(viewHolder); 11141 return false; 11142 } 11143 ViewCompat.setImportantForAccessibility(viewHolder.itemView, importantForAccessibility); 11144 return true; 11145 } 11146 11147 void dispatchPendingImportantForAccessibilityChanges() { 11148 for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) { 11149 ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); 11150 if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { 11151 continue; 11152 } 11153 int state = viewHolder.mPendingAccessibilityState; 11154 if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { 11155 //noinspection WrongConstant 11156 ViewCompat.setImportantForAccessibility(viewHolder.itemView, state); 11157 viewHolder.mPendingAccessibilityState = 11158 ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; 11159 } 11160 } 11161 mPendingAccessibilityImportanceChange.clear(); 11162 } 11163 11164 int getAdapterPositionFor(ViewHolder viewHolder) { 11165 if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 11166 | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) 11167 || !viewHolder.isBound()) { 11168 return RecyclerView.NO_POSITION; 11169 } 11170 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); 11171 } 11172 11173 @VisibleForTesting 11174 void initFastScroller(StateListDrawable verticalThumbDrawable, 11175 Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, 11176 Drawable horizontalTrackDrawable) { 11177 if (verticalThumbDrawable == null || verticalTrackDrawable == null 11178 || horizontalThumbDrawable == null || horizontalTrackDrawable == null) { 11179 throw new IllegalArgumentException( 11180 "Trying to set fast scroller without both required drawables." + exceptionLabel()); 11181 } 11182 11183 Resources resources = getContext().getResources(); 11184 new FastScroller(this, verticalThumbDrawable, verticalTrackDrawable, 11185 horizontalThumbDrawable, horizontalTrackDrawable, 11186 resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness), 11187 resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range), 11188 resources.getDimensionPixelOffset(R.dimen.fastscroll_margin)); 11189 } 11190 11191 // NestedScrollingChild 11192 11193 @Override 11194 public void setNestedScrollingEnabled(boolean enabled) { 11195 getScrollingChildHelper().setNestedScrollingEnabled(enabled); 11196 } 11197 11198 @Override 11199 public boolean isNestedScrollingEnabled() { 11200 return getScrollingChildHelper().isNestedScrollingEnabled(); 11201 } 11202 11203 @Override 11204 public boolean startNestedScroll(int axes) { 11205 return getScrollingChildHelper().startNestedScroll(axes); 11206 } 11207 11208 @Override 11209 public boolean startNestedScroll(int axes, int type) { 11210 return getScrollingChildHelper().startNestedScroll(axes, type); 11211 } 11212 11213 @Override 11214 public void stopNestedScroll() { 11215 getScrollingChildHelper().stopNestedScroll(); 11216 } 11217 11218 @Override 11219 public void stopNestedScroll(int type) { 11220 getScrollingChildHelper().stopNestedScroll(type); 11221 } 11222 11223 @Override 11224 public boolean hasNestedScrollingParent() { 11225 return getScrollingChildHelper().hasNestedScrollingParent(); 11226 } 11227 11228 @Override 11229 public boolean hasNestedScrollingParent(int type) { 11230 return getScrollingChildHelper().hasNestedScrollingParent(type); 11231 } 11232 11233 @Override 11234 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 11235 int dyUnconsumed, int[] offsetInWindow) { 11236 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 11237 dxUnconsumed, dyUnconsumed, offsetInWindow); 11238 } 11239 11240 @Override 11241 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 11242 int dyUnconsumed, int[] offsetInWindow, int type) { 11243 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 11244 dxUnconsumed, dyUnconsumed, offsetInWindow, type); 11245 } 11246 11247 @Override 11248 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 11249 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 11250 } 11251 11252 @Override 11253 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, 11254 int type) { 11255 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, 11256 type); 11257 } 11258 11259 @Override 11260 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 11261 return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); 11262 } 11263 11264 @Override 11265 public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 11266 return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); 11267 } 11268 11269 /** 11270 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 11271 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 11272 * to create their own subclass of this <code>LayoutParams</code> class 11273 * to store any additional required per-child view metadata about the layout. 11274 */ 11275 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { 11276 ViewHolder mViewHolder; 11277 final Rect mDecorInsets = new Rect(); 11278 boolean mInsetsDirty = true; 11279 // Flag is set to true if the view is bound while it is detached from RV. 11280 // In this case, we need to manually call invalidate after view is added to guarantee that 11281 // invalidation is populated through the View hierarchy 11282 boolean mPendingInvalidate = false; 11283 11284 public LayoutParams(Context c, AttributeSet attrs) { 11285 super(c, attrs); 11286 } 11287 11288 public LayoutParams(int width, int height) { 11289 super(width, height); 11290 } 11291 11292 public LayoutParams(MarginLayoutParams source) { 11293 super(source); 11294 } 11295 11296 public LayoutParams(ViewGroup.LayoutParams source) { 11297 super(source); 11298 } 11299 11300 public LayoutParams(LayoutParams source) { 11301 super((ViewGroup.LayoutParams) source); 11302 } 11303 11304 /** 11305 * Returns true if the view this LayoutParams is attached to needs to have its content 11306 * updated from the corresponding adapter. 11307 * 11308 * @return true if the view should have its content updated 11309 */ 11310 public boolean viewNeedsUpdate() { 11311 return mViewHolder.needsUpdate(); 11312 } 11313 11314 /** 11315 * Returns true if the view this LayoutParams is attached to is now representing 11316 * potentially invalid data. A LayoutManager should scrap/recycle it. 11317 * 11318 * @return true if the view is invalid 11319 */ 11320 public boolean isViewInvalid() { 11321 return mViewHolder.isInvalid(); 11322 } 11323 11324 /** 11325 * Returns true if the adapter data item corresponding to the view this LayoutParams 11326 * is attached to has been removed from the data set. A LayoutManager may choose to 11327 * treat it differently in order to animate its outgoing or disappearing state. 11328 * 11329 * @return true if the item the view corresponds to was removed from the data set 11330 */ 11331 public boolean isItemRemoved() { 11332 return mViewHolder.isRemoved(); 11333 } 11334 11335 /** 11336 * Returns true if the adapter data item corresponding to the view this LayoutParams 11337 * is attached to has been changed in the data set. A LayoutManager may choose to 11338 * treat it differently in order to animate its changing state. 11339 * 11340 * @return true if the item the view corresponds to was changed in the data set 11341 */ 11342 public boolean isItemChanged() { 11343 return mViewHolder.isUpdated(); 11344 } 11345 11346 /** 11347 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} 11348 */ 11349 @Deprecated 11350 public int getViewPosition() { 11351 return mViewHolder.getPosition(); 11352 } 11353 11354 /** 11355 * Returns the adapter position that the view this LayoutParams is attached to corresponds 11356 * to as of latest layout calculation. 11357 * 11358 * @return the adapter position this view as of latest layout pass 11359 */ 11360 public int getViewLayoutPosition() { 11361 return mViewHolder.getLayoutPosition(); 11362 } 11363 11364 /** 11365 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 11366 * corresponds to. 11367 * 11368 * @return the up-to-date adapter position this view. It may return 11369 * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or 11370 * its up-to-date position cannot be calculated. 11371 */ 11372 public int getViewAdapterPosition() { 11373 return mViewHolder.getAdapterPosition(); 11374 } 11375 } 11376 11377 /** 11378 * Observer base class for watching changes to an {@link Adapter}. 11379 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 11380 */ 11381 public abstract static class AdapterDataObserver { 11382 public void onChanged() { 11383 // Do nothing 11384 } 11385 11386 public void onItemRangeChanged(int positionStart, int itemCount) { 11387 // do nothing 11388 } 11389 11390 public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { 11391 // fallback to onItemRangeChanged(positionStart, itemCount) if app 11392 // does not override this method. 11393 onItemRangeChanged(positionStart, itemCount); 11394 } 11395 11396 public void onItemRangeInserted(int positionStart, int itemCount) { 11397 // do nothing 11398 } 11399 11400 public void onItemRangeRemoved(int positionStart, int itemCount) { 11401 // do nothing 11402 } 11403 11404 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 11405 // do nothing 11406 } 11407 } 11408 11409 /** 11410 * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and 11411 * provides methods to trigger a programmatic scroll.</p> 11412 * 11413 * @see LinearSmoothScroller 11414 */ 11415 public abstract static class SmoothScroller { 11416 11417 private int mTargetPosition = RecyclerView.NO_POSITION; 11418 11419 private RecyclerView mRecyclerView; 11420 11421 private LayoutManager mLayoutManager; 11422 11423 private boolean mPendingInitialRun; 11424 11425 private boolean mRunning; 11426 11427 private View mTargetView; 11428 11429 private final Action mRecyclingAction; 11430 11431 public SmoothScroller() { 11432 mRecyclingAction = new Action(0, 0); 11433 } 11434 11435 /** 11436 * Starts a smooth scroll for the given target position. 11437 * <p>In each animation step, {@link RecyclerView} will check 11438 * for the target view and call either 11439 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 11440 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until 11441 * SmoothScroller is stopped.</p> 11442 * 11443 * <p>Note that if RecyclerView finds the target view, it will automatically stop the 11444 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will 11445 * stop calling SmoothScroller in each animation step.</p> 11446 */ 11447 void start(RecyclerView recyclerView, LayoutManager layoutManager) { 11448 mRecyclerView = recyclerView; 11449 mLayoutManager = layoutManager; 11450 if (mTargetPosition == RecyclerView.NO_POSITION) { 11451 throw new IllegalArgumentException("Invalid target position"); 11452 } 11453 mRecyclerView.mState.mTargetPosition = mTargetPosition; 11454 mRunning = true; 11455 mPendingInitialRun = true; 11456 mTargetView = findViewByPosition(getTargetPosition()); 11457 onStart(); 11458 mRecyclerView.mViewFlinger.postOnAnimation(); 11459 } 11460 11461 public void setTargetPosition(int targetPosition) { 11462 mTargetPosition = targetPosition; 11463 } 11464 11465 /** 11466 * Compute the scroll vector for a given target position. 11467 * <p> 11468 * This method can return null if the layout manager cannot calculate a scroll vector 11469 * for the given position (e.g. it has no current scroll position). 11470 * 11471 * @param targetPosition the position to which the scroller is scrolling 11472 * 11473 * @return the scroll vector for a given target position 11474 */ 11475 @Nullable 11476 public PointF computeScrollVectorForPosition(int targetPosition) { 11477 LayoutManager layoutManager = getLayoutManager(); 11478 if (layoutManager instanceof ScrollVectorProvider) { 11479 return ((ScrollVectorProvider) layoutManager) 11480 .computeScrollVectorForPosition(targetPosition); 11481 } 11482 Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager" 11483 + " does not implement " + ScrollVectorProvider.class.getCanonicalName()); 11484 return null; 11485 } 11486 11487 /** 11488 * @return The LayoutManager to which this SmoothScroller is attached. Will return 11489 * <code>null</code> after the SmoothScroller is stopped. 11490 */ 11491 @Nullable 11492 public LayoutManager getLayoutManager() { 11493 return mLayoutManager; 11494 } 11495 11496 /** 11497 * Stops running the SmoothScroller in each animation callback. Note that this does not 11498 * cancel any existing {@link Action} updated by 11499 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 11500 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. 11501 */ 11502 protected final void stop() { 11503 if (!mRunning) { 11504 return; 11505 } 11506 mRunning = false; 11507 onStop(); 11508 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; 11509 mTargetView = null; 11510 mTargetPosition = RecyclerView.NO_POSITION; 11511 mPendingInitialRun = false; 11512 // trigger a cleanup 11513 mLayoutManager.onSmoothScrollerStopped(this); 11514 // clear references to avoid any potential leak by a custom smooth scroller 11515 mLayoutManager = null; 11516 mRecyclerView = null; 11517 } 11518 11519 /** 11520 * Returns true if SmoothScroller has been started but has not received the first 11521 * animation 11522 * callback yet. 11523 * 11524 * @return True if this SmoothScroller is waiting to start 11525 */ 11526 public boolean isPendingInitialRun() { 11527 return mPendingInitialRun; 11528 } 11529 11530 11531 /** 11532 * @return True if SmoothScroller is currently active 11533 */ 11534 public boolean isRunning() { 11535 return mRunning; 11536 } 11537 11538 /** 11539 * Returns the adapter position of the target item 11540 * 11541 * @return Adapter position of the target item or 11542 * {@link RecyclerView#NO_POSITION} if no target view is set. 11543 */ 11544 public int getTargetPosition() { 11545 return mTargetPosition; 11546 } 11547 11548 private void onAnimation(int dx, int dy) { 11549 // TODO(b/72745539): If mRunning is false, we call stop, which is a no op if mRunning 11550 // is false. Also, if recyclerView is null, we call stop, and stop assumes recyclerView 11551 // is not null (as does the code following this block). This should be cleaned up. 11552 final RecyclerView recyclerView = mRecyclerView; 11553 if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) { 11554 stop(); 11555 } 11556 11557 // The following if block exists to have the LayoutManager scroll 1 pixel in the correct 11558 // direction in order to cause the LayoutManager to draw two pages worth of views so 11559 // that the target view may be found before scrolling any further. This is done to 11560 // prevent an initial scroll distance from scrolling past the view, which causes a 11561 // jittery looking animation. (This block also necessarily sets mPendingInitialRun to 11562 // false if it was true). 11563 if (mPendingInitialRun && mTargetView == null && mLayoutManager != null) { 11564 PointF pointF = computeScrollVectorForPosition(mTargetPosition); 11565 if (pointF != null && (pointF.x != 0 || pointF.y != 0)) { 11566 recyclerView.scrollStep( 11567 (int) Math.signum(pointF.x), 11568 (int) Math.signum(pointF.y), 11569 null); 11570 } 11571 } 11572 11573 mPendingInitialRun = false; 11574 11575 if (mTargetView != null) { 11576 // verify target position 11577 if (getChildPosition(mTargetView) == mTargetPosition) { 11578 onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction); 11579 mRecyclingAction.runIfNecessary(recyclerView); 11580 stop(); 11581 } else { 11582 Log.e(TAG, "Passed over target position while smooth scrolling."); 11583 mTargetView = null; 11584 } 11585 } 11586 if (mRunning) { 11587 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction); 11588 boolean hadJumpTarget = mRecyclingAction.hasJumpTarget(); 11589 mRecyclingAction.runIfNecessary(recyclerView); 11590 if (hadJumpTarget) { 11591 // It is not stopped so needs to be restarted 11592 if (mRunning) { 11593 mPendingInitialRun = true; 11594 recyclerView.mViewFlinger.postOnAnimation(); 11595 } else { 11596 // TODO(b/72745539): stop() is a no-op if mRunning is false, so this can be 11597 // removed. 11598 stop(); // done 11599 } 11600 } 11601 } 11602 } 11603 11604 /** 11605 * @see RecyclerView#getChildLayoutPosition(android.view.View) 11606 */ 11607 public int getChildPosition(View view) { 11608 return mRecyclerView.getChildLayoutPosition(view); 11609 } 11610 11611 /** 11612 * @see RecyclerView.LayoutManager#getChildCount() 11613 */ 11614 public int getChildCount() { 11615 return mRecyclerView.mLayout.getChildCount(); 11616 } 11617 11618 /** 11619 * @see RecyclerView.LayoutManager#findViewByPosition(int) 11620 */ 11621 public View findViewByPosition(int position) { 11622 return mRecyclerView.mLayout.findViewByPosition(position); 11623 } 11624 11625 /** 11626 * @see RecyclerView#scrollToPosition(int) 11627 * @deprecated Use {@link Action#jumpTo(int)}. 11628 */ 11629 @Deprecated 11630 public void instantScrollToPosition(int position) { 11631 mRecyclerView.scrollToPosition(position); 11632 } 11633 11634 protected void onChildAttachedToWindow(View child) { 11635 if (getChildPosition(child) == getTargetPosition()) { 11636 mTargetView = child; 11637 if (DEBUG) { 11638 Log.d(TAG, "smooth scroll target view has been attached"); 11639 } 11640 } 11641 } 11642 11643 /** 11644 * Normalizes the vector. 11645 * @param scrollVector The vector that points to the target scroll position 11646 */ 11647 protected void normalize(@NonNull PointF scrollVector) { 11648 final float magnitude = (float) Math.sqrt(scrollVector.x * scrollVector.x 11649 + scrollVector.y * scrollVector.y); 11650 scrollVector.x /= magnitude; 11651 scrollVector.y /= magnitude; 11652 } 11653 11654 /** 11655 * Called when smooth scroll is started. This might be a good time to do setup. 11656 */ 11657 protected abstract void onStart(); 11658 11659 /** 11660 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. 11661 * @see #stop() 11662 */ 11663 protected abstract void onStop(); 11664 11665 /** 11666 * <p>RecyclerView will call this method each time it scrolls until it can find the target 11667 * position in the layout.</p> 11668 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the 11669 * provided {@link Action} to define the next scroll.</p> 11670 * 11671 * @param dx Last scroll amount horizontally 11672 * @param dy Last scroll amount vertically 11673 * @param state Transient state of RecyclerView 11674 * @param action If you want to trigger a new smooth scroll and cancel the previous one, 11675 * update this object. 11676 */ 11677 protected abstract void onSeekTargetStep(@Px int dx, @Px int dy, @NonNull State state, 11678 @NonNull Action action); 11679 11680 /** 11681 * Called when the target position is laid out. This is the last callback SmoothScroller 11682 * will receive and it should update the provided {@link Action} to define the scroll 11683 * details towards the target view. 11684 * @param targetView The view element which render the target position. 11685 * @param state Transient state of RecyclerView 11686 * @param action Action instance that you should update to define final scroll action 11687 * towards the targetView 11688 */ 11689 protected abstract void onTargetFound(@NonNull View targetView, @NonNull State state, 11690 @NonNull Action action); 11691 11692 /** 11693 * Holds information about a smooth scroll request by a {@link SmoothScroller}. 11694 */ 11695 public static class Action { 11696 11697 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; 11698 11699 private int mDx; 11700 11701 private int mDy; 11702 11703 private int mDuration; 11704 11705 private int mJumpToPosition = NO_POSITION; 11706 11707 private Interpolator mInterpolator; 11708 11709 private boolean mChanged = false; 11710 11711 // we track this variable to inform custom implementer if they are updating the action 11712 // in every animation callback 11713 private int mConsecutiveUpdates = 0; 11714 11715 /** 11716 * @param dx Pixels to scroll horizontally 11717 * @param dy Pixels to scroll vertically 11718 */ 11719 public Action(@Px int dx, @Px int dy) { 11720 this(dx, dy, UNDEFINED_DURATION, null); 11721 } 11722 11723 /** 11724 * @param dx Pixels to scroll horizontally 11725 * @param dy Pixels to scroll vertically 11726 * @param duration Duration of the animation in milliseconds 11727 */ 11728 public Action(@Px int dx, @Px int dy, int duration) { 11729 this(dx, dy, duration, null); 11730 } 11731 11732 /** 11733 * @param dx Pixels to scroll horizontally 11734 * @param dy Pixels to scroll vertically 11735 * @param duration Duration of the animation in milliseconds 11736 * @param interpolator Interpolator to be used when calculating scroll position in each 11737 * animation step 11738 */ 11739 public Action(@Px int dx, @Px int dy, int duration, 11740 @Nullable Interpolator interpolator) { 11741 mDx = dx; 11742 mDy = dy; 11743 mDuration = duration; 11744 mInterpolator = interpolator; 11745 } 11746 11747 /** 11748 * Instead of specifying pixels to scroll, use the target position to jump using 11749 * {@link RecyclerView#scrollToPosition(int)}. 11750 * <p> 11751 * You may prefer using this method if scroll target is really far away and you prefer 11752 * to jump to a location and smooth scroll afterwards. 11753 * <p> 11754 * Note that calling this method takes priority over other update methods such as 11755 * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)}, 11756 * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call 11757 * {@link #jumpTo(int)}, the other changes will not be considered for this animation 11758 * frame. 11759 * 11760 * @param targetPosition The target item position to scroll to using instant scrolling. 11761 */ 11762 public void jumpTo(int targetPosition) { 11763 mJumpToPosition = targetPosition; 11764 } 11765 11766 boolean hasJumpTarget() { 11767 return mJumpToPosition >= 0; 11768 } 11769 11770 void runIfNecessary(RecyclerView recyclerView) { 11771 if (mJumpToPosition >= 0) { 11772 final int position = mJumpToPosition; 11773 mJumpToPosition = NO_POSITION; 11774 recyclerView.jumpToPositionForSmoothScroller(position); 11775 mChanged = false; 11776 return; 11777 } 11778 if (mChanged) { 11779 validate(); 11780 if (mInterpolator == null) { 11781 if (mDuration == UNDEFINED_DURATION) { 11782 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); 11783 } else { 11784 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); 11785 } 11786 } else { 11787 recyclerView.mViewFlinger.smoothScrollBy( 11788 mDx, mDy, mDuration, mInterpolator); 11789 } 11790 mConsecutiveUpdates++; 11791 if (mConsecutiveUpdates > 10) { 11792 // A new action is being set in every animation step. This looks like a bad 11793 // implementation. Inform developer. 11794 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" 11795 + " you are not changing it unless necessary"); 11796 } 11797 mChanged = false; 11798 } else { 11799 mConsecutiveUpdates = 0; 11800 } 11801 } 11802 11803 private void validate() { 11804 if (mInterpolator != null && mDuration < 1) { 11805 throw new IllegalStateException("If you provide an interpolator, you must" 11806 + " set a positive duration"); 11807 } else if (mDuration < 1) { 11808 throw new IllegalStateException("Scroll duration must be a positive number"); 11809 } 11810 } 11811 11812 @Px 11813 public int getDx() { 11814 return mDx; 11815 } 11816 11817 public void setDx(@Px int dx) { 11818 mChanged = true; 11819 mDx = dx; 11820 } 11821 11822 @Px 11823 public int getDy() { 11824 return mDy; 11825 } 11826 11827 public void setDy(@Px int dy) { 11828 mChanged = true; 11829 mDy = dy; 11830 } 11831 11832 public int getDuration() { 11833 return mDuration; 11834 } 11835 11836 public void setDuration(int duration) { 11837 mChanged = true; 11838 mDuration = duration; 11839 } 11840 11841 @Nullable 11842 public Interpolator getInterpolator() { 11843 return mInterpolator; 11844 } 11845 11846 /** 11847 * Sets the interpolator to calculate scroll steps 11848 * @param interpolator The interpolator to use. If you specify an interpolator, you must 11849 * also set the duration. 11850 * @see #setDuration(int) 11851 */ 11852 public void setInterpolator(@Nullable Interpolator interpolator) { 11853 mChanged = true; 11854 mInterpolator = interpolator; 11855 } 11856 11857 /** 11858 * Updates the action with given parameters. 11859 * @param dx Pixels to scroll horizontally 11860 * @param dy Pixels to scroll vertically 11861 * @param duration Duration of the animation in milliseconds 11862 * @param interpolator Interpolator to be used when calculating scroll position in each 11863 * animation step 11864 */ 11865 public void update(@Px int dx, @Px int dy, int duration, 11866 @Nullable Interpolator interpolator) { 11867 mDx = dx; 11868 mDy = dy; 11869 mDuration = duration; 11870 mInterpolator = interpolator; 11871 mChanged = true; 11872 } 11873 } 11874 11875 /** 11876 * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} 11877 * to provide a hint to a {@link SmoothScroller} about the location of the target position. 11878 */ 11879 public interface ScrollVectorProvider { 11880 /** 11881 * Should calculate the vector that points to the direction where the target position 11882 * can be found. 11883 * <p> 11884 * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards 11885 * the target position. 11886 * <p> 11887 * The magnitude of the vector is not important. It is always normalized before being 11888 * used by the {@link LinearSmoothScroller}. 11889 * <p> 11890 * LayoutManager should not check whether the position exists in the adapter or not. 11891 * 11892 * @param targetPosition the target position to which the returned vector should point 11893 * 11894 * @return the scroll vector for a given position. 11895 */ 11896 @Nullable 11897 PointF computeScrollVectorForPosition(int targetPosition); 11898 } 11899 } 11900 11901 static class AdapterDataObservable extends Observable<AdapterDataObserver> { 11902 public boolean hasObservers() { 11903 return !mObservers.isEmpty(); 11904 } 11905 11906 public void notifyChanged() { 11907 // since onChanged() is implemented by the app, it could do anything, including 11908 // removing itself from {@link mObservers} - and that could cause problems if 11909 // an iterator is used on the ArrayList {@link mObservers}. 11910 // to avoid such problems, just march thru the list in the reverse order. 11911 for (int i = mObservers.size() - 1; i >= 0; i--) { 11912 mObservers.get(i).onChanged(); 11913 } 11914 } 11915 11916 public void notifyItemRangeChanged(int positionStart, int itemCount) { 11917 notifyItemRangeChanged(positionStart, itemCount, null); 11918 } 11919 11920 public void notifyItemRangeChanged(int positionStart, int itemCount, 11921 @Nullable Object payload) { 11922 // since onItemRangeChanged() is implemented by the app, it could do anything, including 11923 // removing itself from {@link mObservers} - and that could cause problems if 11924 // an iterator is used on the ArrayList {@link mObservers}. 11925 // to avoid such problems, just march thru the list in the reverse order. 11926 for (int i = mObservers.size() - 1; i >= 0; i--) { 11927 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); 11928 } 11929 } 11930 11931 public void notifyItemRangeInserted(int positionStart, int itemCount) { 11932 // since onItemRangeInserted() is implemented by the app, it could do anything, 11933 // including removing itself from {@link mObservers} - and that could cause problems if 11934 // an iterator is used on the ArrayList {@link mObservers}. 11935 // to avoid such problems, just march thru the list in the reverse order. 11936 for (int i = mObservers.size() - 1; i >= 0; i--) { 11937 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 11938 } 11939 } 11940 11941 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 11942 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 11943 // removing itself from {@link mObservers} - and that could cause problems if 11944 // an iterator is used on the ArrayList {@link mObservers}. 11945 // to avoid such problems, just march thru the list in the reverse order. 11946 for (int i = mObservers.size() - 1; i >= 0; i--) { 11947 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 11948 } 11949 } 11950 11951 public void notifyItemMoved(int fromPosition, int toPosition) { 11952 for (int i = mObservers.size() - 1; i >= 0; i--) { 11953 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); 11954 } 11955 } 11956 } 11957 11958 /** 11959 * This is public so that the CREATOR can be accessed on cold launch. 11960 * @hide 11961 */ 11962 @RestrictTo(LIBRARY_GROUP) 11963 public static class SavedState extends AbsSavedState { 11964 11965 Parcelable mLayoutState; 11966 11967 /** 11968 * called by CREATOR 11969 */ 11970 SavedState(Parcel in, ClassLoader loader) { 11971 super(in, loader); 11972 mLayoutState = in.readParcelable( 11973 loader != null ? loader : LayoutManager.class.getClassLoader()); 11974 } 11975 11976 /** 11977 * Called by onSaveInstanceState 11978 */ 11979 SavedState(Parcelable superState) { 11980 super(superState); 11981 } 11982 11983 @Override 11984 public void writeToParcel(Parcel dest, int flags) { 11985 super.writeToParcel(dest, flags); 11986 dest.writeParcelable(mLayoutState, 0); 11987 } 11988 11989 void copyFrom(SavedState other) { 11990 mLayoutState = other.mLayoutState; 11991 } 11992 11993 public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { 11994 @Override 11995 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 11996 return new SavedState(in, loader); 11997 } 11998 11999 @Override 12000 public SavedState createFromParcel(Parcel in) { 12001 return new SavedState(in, null); 12002 } 12003 12004 @Override 12005 public SavedState[] newArray(int size) { 12006 return new SavedState[size]; 12007 } 12008 }; 12009 } 12010 12011 /** 12012 * <p>Contains useful information about the current RecyclerView state like target scroll 12013 * position or view focus. State object can also keep arbitrary data, identified by resource 12014 * ids.</p> 12015 * <p>Often times, RecyclerView components will need to pass information between each other. 12016 * To provide a well defined data bus between components, RecyclerView passes the same State 12017 * object to component callbacks and these components can use it to exchange data.</p> 12018 * <p>If you implement custom components, you can use State's put/get/remove methods to pass 12019 * data between your components without needing to manage their lifecycles.</p> 12020 */ 12021 public static class State { 12022 static final int STEP_START = 1; 12023 static final int STEP_LAYOUT = 1 << 1; 12024 static final int STEP_ANIMATIONS = 1 << 2; 12025 12026 void assertLayoutStep(int accepted) { 12027 if ((accepted & mLayoutStep) == 0) { 12028 throw new IllegalStateException("Layout state should be one of " 12029 + Integer.toBinaryString(accepted) + " but it is " 12030 + Integer.toBinaryString(mLayoutStep)); 12031 } 12032 } 12033 12034 12035 /** Owned by SmoothScroller */ 12036 private int mTargetPosition = RecyclerView.NO_POSITION; 12037 12038 private SparseArray<Object> mData; 12039 12040 //////////////////////////////////////////////////////////////////////////////////////////// 12041 // Fields below are carried from one layout pass to the next 12042 //////////////////////////////////////////////////////////////////////////////////////////// 12043 12044 /** 12045 * Number of items adapter had in the previous layout. 12046 */ 12047 int mPreviousLayoutItemCount = 0; 12048 12049 /** 12050 * Number of items that were NOT laid out but has been deleted from the adapter after the 12051 * previous layout. 12052 */ 12053 int mDeletedInvisibleItemCountSincePreviousLayout = 0; 12054 12055 //////////////////////////////////////////////////////////////////////////////////////////// 12056 // Fields below must be updated or cleared before they are used (generally before a pass) 12057 //////////////////////////////////////////////////////////////////////////////////////////// 12058 12059 @IntDef(flag = true, value = { 12060 STEP_START, STEP_LAYOUT, STEP_ANIMATIONS 12061 }) 12062 @Retention(RetentionPolicy.SOURCE) 12063 @interface LayoutState {} 12064 12065 @LayoutState 12066 int mLayoutStep = STEP_START; 12067 12068 /** 12069 * Number of items adapter has. 12070 */ 12071 int mItemCount = 0; 12072 12073 boolean mStructureChanged = false; 12074 12075 /** 12076 * True if the associated {@link RecyclerView} is in the pre-layout step where it is having 12077 * its {@link LayoutManager} layout items where they will be at the beginning of a set of 12078 * predictive item animations. 12079 */ 12080 boolean mInPreLayout = false; 12081 12082 boolean mTrackOldChangeHolders = false; 12083 12084 boolean mIsMeasuring = false; 12085 12086 //////////////////////////////////////////////////////////////////////////////////////////// 12087 // Fields below are always reset outside of the pass (or passes) that use them 12088 //////////////////////////////////////////////////////////////////////////////////////////// 12089 12090 boolean mRunSimpleAnimations = false; 12091 12092 boolean mRunPredictiveAnimations = false; 12093 12094 /** 12095 * This data is saved before a layout calculation happens. After the layout is finished, 12096 * if the previously focused view has been replaced with another view for the same item, we 12097 * move the focus to the new item automatically. 12098 */ 12099 int mFocusedItemPosition; 12100 long mFocusedItemId; 12101 // when a sub child has focus, record its id and see if we can directly request focus on 12102 // that one instead 12103 int mFocusedSubChildId; 12104 12105 int mRemainingScrollHorizontal; 12106 int mRemainingScrollVertical; 12107 12108 //////////////////////////////////////////////////////////////////////////////////////////// 12109 12110 State reset() { 12111 mTargetPosition = RecyclerView.NO_POSITION; 12112 if (mData != null) { 12113 mData.clear(); 12114 } 12115 mItemCount = 0; 12116 mStructureChanged = false; 12117 mIsMeasuring = false; 12118 return this; 12119 } 12120 12121 /** 12122 * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially 12123 * prior to any layout passes. 12124 * 12125 * <p>Don't touch any state stored between layout passes, only reset per-layout state, so 12126 * that Recycler#getViewForPosition() can function safely.</p> 12127 */ 12128 void prepareForNestedPrefetch(Adapter adapter) { 12129 mLayoutStep = STEP_START; 12130 mItemCount = adapter.getItemCount(); 12131 mInPreLayout = false; 12132 mTrackOldChangeHolders = false; 12133 mIsMeasuring = false; 12134 } 12135 12136 /** 12137 * Returns true if the RecyclerView is currently measuring the layout. This value is 12138 * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView 12139 * has non-exact measurement specs. 12140 * <p> 12141 * Note that if the LayoutManager supports predictive animations and it is calculating the 12142 * pre-layout step, this value will be {@code false} even if the RecyclerView is in 12143 * {@code onMeasure} call. This is because pre-layout means the previous state of the 12144 * RecyclerView and measurements made for that state cannot change the RecyclerView's size. 12145 * LayoutManager is always guaranteed to receive another call to 12146 * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. 12147 * 12148 * @return True if the RecyclerView is currently calculating its bounds, false otherwise. 12149 */ 12150 public boolean isMeasuring() { 12151 return mIsMeasuring; 12152 } 12153 12154 /** 12155 * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its 12156 * {@link LayoutManager} layout items where they will be at the beginning of a set of 12157 * predictive item animations. 12158 */ 12159 public boolean isPreLayout() { 12160 return mInPreLayout; 12161 } 12162 12163 /** 12164 * Returns whether RecyclerView will run predictive animations in this layout pass 12165 * or not. 12166 * 12167 * @return true if RecyclerView is calculating predictive animations to be run at the end 12168 * of the layout pass. 12169 */ 12170 public boolean willRunPredictiveAnimations() { 12171 return mRunPredictiveAnimations; 12172 } 12173 12174 /** 12175 * Returns whether RecyclerView will run simple animations in this layout pass 12176 * or not. 12177 * 12178 * @return true if RecyclerView is calculating simple animations to be run at the end of 12179 * the layout pass. 12180 */ 12181 public boolean willRunSimpleAnimations() { 12182 return mRunSimpleAnimations; 12183 } 12184 12185 /** 12186 * Removes the mapping from the specified id, if there was any. 12187 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to 12188 * preserve cross functionality and avoid conflicts. 12189 */ 12190 public void remove(int resourceId) { 12191 if (mData == null) { 12192 return; 12193 } 12194 mData.remove(resourceId); 12195 } 12196 12197 /** 12198 * Gets the Object mapped from the specified id, or <code>null</code> 12199 * if no such data exists. 12200 * 12201 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* 12202 * to 12203 * preserve cross functionality and avoid conflicts. 12204 */ 12205 @SuppressWarnings("TypeParameterUnusedInFormals") 12206 public <T> T get(int resourceId) { 12207 if (mData == null) { 12208 return null; 12209 } 12210 return (T) mData.get(resourceId); 12211 } 12212 12213 /** 12214 * Adds a mapping from the specified id to the specified value, replacing the previous 12215 * mapping from the specified key if there was one. 12216 * 12217 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to 12218 * preserve cross functionality and avoid conflicts. 12219 * @param data The data you want to associate with the resourceId. 12220 */ 12221 public void put(int resourceId, Object data) { 12222 if (mData == null) { 12223 mData = new SparseArray<Object>(); 12224 } 12225 mData.put(resourceId, data); 12226 } 12227 12228 /** 12229 * If scroll is triggered to make a certain item visible, this value will return the 12230 * adapter index of that item. 12231 * @return Adapter index of the target item or 12232 * {@link RecyclerView#NO_POSITION} if there is no target 12233 * position. 12234 */ 12235 public int getTargetScrollPosition() { 12236 return mTargetPosition; 12237 } 12238 12239 /** 12240 * Returns if current scroll has a target position. 12241 * @return true if scroll is being triggered to make a certain position visible 12242 * @see #getTargetScrollPosition() 12243 */ 12244 public boolean hasTargetScrollPosition() { 12245 return mTargetPosition != RecyclerView.NO_POSITION; 12246 } 12247 12248 /** 12249 * @return true if the structure of the data set has changed since the last call to 12250 * onLayoutChildren, false otherwise 12251 */ 12252 public boolean didStructureChange() { 12253 return mStructureChanged; 12254 } 12255 12256 /** 12257 * Returns the total number of items that can be laid out. Note that this number is not 12258 * necessarily equal to the number of items in the adapter, so you should always use this 12259 * number for your position calculations and never access the adapter directly. 12260 * <p> 12261 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter 12262 * data changes on existing Views. These calculations are used to decide which animations 12263 * should be run. 12264 * <p> 12265 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to 12266 * present the correct state to LayoutManager in pre-layout pass. 12267 * <p> 12268 * For example, a newly added item is not included in pre-layout item count because 12269 * pre-layout reflects the contents of the adapter before the item is added. Behind the 12270 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that 12271 * LayoutManager does not know about the new item's existence in pre-layout. The item will 12272 * be available in second layout pass and will be included in the item count. Similar 12273 * adjustments are made for moved and removed items as well. 12274 * <p> 12275 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. 12276 * 12277 * @return The number of items currently available 12278 * @see LayoutManager#getItemCount() 12279 */ 12280 public int getItemCount() { 12281 return mInPreLayout 12282 ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) 12283 : mItemCount; 12284 } 12285 12286 /** 12287 * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/ 12288 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 12289 * other than {@link #SCROLL_STATE_SETTLING}. 12290 * 12291 * @return Remaining horizontal scroll distance 12292 */ 12293 public int getRemainingScrollHorizontal() { 12294 return mRemainingScrollHorizontal; 12295 } 12296 12297 /** 12298 * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/ 12299 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 12300 * other than {@link #SCROLL_STATE_SETTLING}. 12301 * 12302 * @return Remaining vertical scroll distance 12303 */ 12304 public int getRemainingScrollVertical() { 12305 return mRemainingScrollVertical; 12306 } 12307 12308 @Override 12309 public String toString() { 12310 return "State{" 12311 + "mTargetPosition=" + mTargetPosition 12312 + ", mData=" + mData 12313 + ", mItemCount=" + mItemCount 12314 + ", mIsMeasuring=" + mIsMeasuring 12315 + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount 12316 + ", mDeletedInvisibleItemCountSincePreviousLayout=" 12317 + mDeletedInvisibleItemCountSincePreviousLayout 12318 + ", mStructureChanged=" + mStructureChanged 12319 + ", mInPreLayout=" + mInPreLayout 12320 + ", mRunSimpleAnimations=" + mRunSimpleAnimations 12321 + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations 12322 + '}'; 12323 } 12324 } 12325 12326 /** 12327 * This class defines the behavior of fling if the developer wishes to handle it. 12328 * <p> 12329 * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. 12330 * 12331 * @see #setOnFlingListener(OnFlingListener) 12332 */ 12333 public abstract static class OnFlingListener { 12334 12335 /** 12336 * Override this to handle a fling given the velocities in both x and y directions. 12337 * Note that this method will only be called if the associated {@link LayoutManager} 12338 * supports scrolling and the fling is not handled by nested scrolls first. 12339 * 12340 * @param velocityX the fling velocity on the X axis 12341 * @param velocityY the fling velocity on the Y axis 12342 * 12343 * @return true if the fling was handled, false otherwise. 12344 */ 12345 public abstract boolean onFling(int velocityX, int velocityY); 12346 } 12347 12348 /** 12349 * Internal listener that manages items after animations finish. This is how items are 12350 * retained (not recycled) during animations, but allowed to be recycled afterwards. 12351 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() 12352 * method on the animator's listener when it is done animating any item. 12353 */ 12354 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { 12355 12356 ItemAnimatorRestoreListener() { 12357 } 12358 12359 @Override 12360 public void onAnimationFinished(ViewHolder item) { 12361 item.setIsRecyclable(true); 12362 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh 12363 item.mShadowedHolder = null; 12364 } 12365 // always null this because an OldViewHolder can never become NewViewHolder w/o being 12366 // recycled. 12367 item.mShadowingHolder = null; 12368 if (!item.shouldBeKeptAsChild()) { 12369 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { 12370 removeDetachedView(item.itemView, false); 12371 } 12372 } 12373 } 12374 } 12375 12376 /** 12377 * This class defines the animations that take place on items as changes are made 12378 * to the adapter. 12379 * 12380 * Subclasses of ItemAnimator can be used to implement custom animations for actions on 12381 * ViewHolder items. The RecyclerView will manage retaining these items while they 12382 * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} 12383 * when a ViewHolder's animation is finished. In other words, there must be a matching 12384 * {@link #dispatchAnimationFinished(ViewHolder)} call for each 12385 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, 12386 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12387 * animateChange()} 12388 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, 12389 * and 12390 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12391 * animateDisappearance()} call. 12392 * 12393 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p> 12394 * 12395 * @see #setItemAnimator(ItemAnimator) 12396 */ 12397 @SuppressWarnings("UnusedParameters") 12398 public abstract static class ItemAnimator { 12399 12400 /** 12401 * The Item represented by this ViewHolder is updated. 12402 * <p> 12403 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12404 */ 12405 public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; 12406 12407 /** 12408 * The Item represented by this ViewHolder is removed from the adapter. 12409 * <p> 12410 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12411 */ 12412 public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; 12413 12414 /** 12415 * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content 12416 * represented by this ViewHolder is invalid. 12417 * <p> 12418 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12419 */ 12420 public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; 12421 12422 /** 12423 * The position of the Item represented by this ViewHolder has been changed. This flag is 12424 * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to 12425 * any adapter change that may have a side effect on this item. (e.g. The item before this 12426 * one has been removed from the Adapter). 12427 * <p> 12428 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12429 */ 12430 public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; 12431 12432 /** 12433 * This ViewHolder was not laid out but has been added to the layout in pre-layout state 12434 * by the {@link LayoutManager}. This means that the item was already in the Adapter but 12435 * invisible and it may become visible in the post layout phase. LayoutManagers may prefer 12436 * to add new items in pre-layout to specify their virtual location when they are invisible 12437 * (e.g. to specify the item should <i>animate in</i> from below the visible area). 12438 * <p> 12439 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12440 */ 12441 public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 12442 ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; 12443 12444 /** 12445 * The set of flags that might be passed to 12446 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12447 */ 12448 @IntDef(flag = true, value = { 12449 FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, 12450 FLAG_APPEARED_IN_PRE_LAYOUT 12451 }) 12452 @Retention(RetentionPolicy.SOURCE) 12453 public @interface AdapterChanges {} 12454 private ItemAnimatorListener mListener = null; 12455 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = 12456 new ArrayList<ItemAnimatorFinishedListener>(); 12457 12458 private long mAddDuration = 120; 12459 private long mRemoveDuration = 120; 12460 private long mMoveDuration = 250; 12461 private long mChangeDuration = 250; 12462 12463 /** 12464 * Gets the current duration for which all move animations will run. 12465 * 12466 * @return The current move duration 12467 */ 12468 public long getMoveDuration() { 12469 return mMoveDuration; 12470 } 12471 12472 /** 12473 * Sets the duration for which all move animations will run. 12474 * 12475 * @param moveDuration The move duration 12476 */ 12477 public void setMoveDuration(long moveDuration) { 12478 mMoveDuration = moveDuration; 12479 } 12480 12481 /** 12482 * Gets the current duration for which all add animations will run. 12483 * 12484 * @return The current add duration 12485 */ 12486 public long getAddDuration() { 12487 return mAddDuration; 12488 } 12489 12490 /** 12491 * Sets the duration for which all add animations will run. 12492 * 12493 * @param addDuration The add duration 12494 */ 12495 public void setAddDuration(long addDuration) { 12496 mAddDuration = addDuration; 12497 } 12498 12499 /** 12500 * Gets the current duration for which all remove animations will run. 12501 * 12502 * @return The current remove duration 12503 */ 12504 public long getRemoveDuration() { 12505 return mRemoveDuration; 12506 } 12507 12508 /** 12509 * Sets the duration for which all remove animations will run. 12510 * 12511 * @param removeDuration The remove duration 12512 */ 12513 public void setRemoveDuration(long removeDuration) { 12514 mRemoveDuration = removeDuration; 12515 } 12516 12517 /** 12518 * Gets the current duration for which all change animations will run. 12519 * 12520 * @return The current change duration 12521 */ 12522 public long getChangeDuration() { 12523 return mChangeDuration; 12524 } 12525 12526 /** 12527 * Sets the duration for which all change animations will run. 12528 * 12529 * @param changeDuration The change duration 12530 */ 12531 public void setChangeDuration(long changeDuration) { 12532 mChangeDuration = changeDuration; 12533 } 12534 12535 /** 12536 * Internal only: 12537 * Sets the listener that must be called when the animator is finished 12538 * animating the item (or immediately if no animation happens). This is set 12539 * internally and is not intended to be set by external code. 12540 * 12541 * @param listener The listener that must be called. 12542 */ 12543 void setListener(ItemAnimatorListener listener) { 12544 mListener = listener; 12545 } 12546 12547 /** 12548 * Called by the RecyclerView before the layout begins. Item animator should record 12549 * necessary information about the View before it is potentially rebound, moved or removed. 12550 * <p> 12551 * The data returned from this method will be passed to the related <code>animate**</code> 12552 * methods. 12553 * <p> 12554 * Note that this method may be called after pre-layout phase if LayoutManager adds new 12555 * Views to the layout in pre-layout pass. 12556 * <p> 12557 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 12558 * the View and the adapter change flags. 12559 * 12560 * @param state The current State of RecyclerView which includes some useful data 12561 * about the layout that will be calculated. 12562 * @param viewHolder The ViewHolder whose information should be recorded. 12563 * @param changeFlags Additional information about what changes happened in the Adapter 12564 * about the Item represented by this ViewHolder. For instance, if 12565 * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. 12566 * @param payloads The payload list that was previously passed to 12567 * {@link Adapter#notifyItemChanged(int, Object)} or 12568 * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. 12569 * 12570 * @return An ItemHolderInfo instance that preserves necessary information about the 12571 * ViewHolder. This object will be passed back to related <code>animate**</code> methods 12572 * after layout is complete. 12573 * 12574 * @see #recordPostLayoutInformation(State, ViewHolder) 12575 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12576 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12577 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12578 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12579 */ 12580 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, 12581 @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, 12582 @NonNull List<Object> payloads) { 12583 return obtainHolderInfo().setFrom(viewHolder); 12584 } 12585 12586 /** 12587 * Called by the RecyclerView after the layout is complete. Item animator should record 12588 * necessary information about the View's final state. 12589 * <p> 12590 * The data returned from this method will be passed to the related <code>animate**</code> 12591 * methods. 12592 * <p> 12593 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 12594 * the View. 12595 * 12596 * @param state The current State of RecyclerView which includes some useful data about 12597 * the layout that will be calculated. 12598 * @param viewHolder The ViewHolder whose information should be recorded. 12599 * 12600 * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. 12601 * This object will be passed back to related <code>animate**</code> methods when 12602 * RecyclerView decides how items should be animated. 12603 * 12604 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 12605 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12606 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12607 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12608 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12609 */ 12610 public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, 12611 @NonNull ViewHolder viewHolder) { 12612 return obtainHolderInfo().setFrom(viewHolder); 12613 } 12614 12615 /** 12616 * Called by the RecyclerView when a ViewHolder has disappeared from the layout. 12617 * <p> 12618 * This means that the View was a child of the LayoutManager when layout started but has 12619 * been removed by the LayoutManager. It might have been removed from the adapter or simply 12620 * become invisible due to other factors. You can distinguish these two cases by checking 12621 * the change flags that were passed to 12622 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12623 * <p> 12624 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 12625 * animation callback method which will be called by the RecyclerView depends on the 12626 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 12627 * LayoutManager's decision whether to layout the changed version of a disappearing 12628 * ViewHolder or not. RecyclerView will call 12629 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12630 * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator 12631 * returns {@code false} from 12632 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 12633 * LayoutManager lays out a new disappearing view that holds the updated information. 12634 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 12635 * <p> 12636 * If LayoutManager supports predictive animations, it might provide a target disappear 12637 * location for the View by laying it out in that location. When that happens, 12638 * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the 12639 * response of that call will be passed to this method as the <code>postLayoutInfo</code>. 12640 * <p> 12641 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 12642 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 12643 * decides not to animate the view). 12644 * 12645 * @param viewHolder The ViewHolder which should be animated 12646 * @param preLayoutInfo The information that was returned from 12647 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12648 * @param postLayoutInfo The information that was returned from 12649 * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be 12650 * null if the LayoutManager did not layout the item. 12651 * 12652 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12653 * false otherwise. 12654 */ 12655 public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, 12656 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); 12657 12658 /** 12659 * Called by the RecyclerView when a ViewHolder is added to the layout. 12660 * <p> 12661 * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started 12662 * but has been added by the LayoutManager. It might be newly added to the adapter or 12663 * simply become visible due to other factors. 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 * Might be null if Item was just added to the adapter or 12673 * LayoutManager does not support predictive animations or it could 12674 * not predict that this ViewHolder will become visible. 12675 * @param postLayoutInfo The information that was returned from {@link 12676 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12677 * 12678 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12679 * false otherwise. 12680 */ 12681 public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, 12682 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12683 12684 /** 12685 * Called by the RecyclerView when a ViewHolder is present in both before and after the 12686 * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call 12687 * for it or a {@link Adapter#notifyDataSetChanged()} call. 12688 * <p> 12689 * This ViewHolder still represents the same data that it was representing when the layout 12690 * started but its position / size may be changed by the LayoutManager. 12691 * <p> 12692 * If the Item's layout position didn't change, RecyclerView still calls this method because 12693 * it does not track this information (or does not necessarily know that an animation is 12694 * not required). Your ItemAnimator should handle this case and if there is nothing to 12695 * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return 12696 * <code>false</code>. 12697 * <p> 12698 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 12699 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 12700 * decides not to animate the view). 12701 * 12702 * @param viewHolder The ViewHolder which should be animated 12703 * @param preLayoutInfo The information that was returned from 12704 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12705 * @param postLayoutInfo The information that was returned from {@link 12706 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12707 * 12708 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12709 * false otherwise. 12710 */ 12711 public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, 12712 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12713 12714 /** 12715 * Called by the RecyclerView when an adapter item is present both before and after the 12716 * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call 12717 * for it. This method may also be called when 12718 * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that 12719 * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when 12720 * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called, 12721 * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be 12722 * called for the new ViewHolder and the old one will be recycled. 12723 * <p> 12724 * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is 12725 * a good possibility that item contents didn't really change but it is rebound from the 12726 * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the 12727 * screen didn't change and your animator should handle this case as well and avoid creating 12728 * unnecessary animations. 12729 * <p> 12730 * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the 12731 * previous presentation of the item as-is and supply a new ViewHolder for the updated 12732 * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. 12733 * This is useful if you don't know the contents of the Item and would like 12734 * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). 12735 * <p> 12736 * When you are writing a custom item animator for your layout, it might be more performant 12737 * and elegant to re-use the same ViewHolder and animate the content changes manually. 12738 * <p> 12739 * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. 12740 * If the Item's view type has changed or ItemAnimator returned <code>false</code> for 12741 * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the 12742 * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances 12743 * which represent the same Item. In that case, only the new ViewHolder is visible 12744 * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. 12745 * <p> 12746 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct 12747 * ViewHolder when their animation is complete 12748 * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to 12749 * animate the view). 12750 * <p> 12751 * If oldHolder and newHolder are the same instance, you should call 12752 * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>. 12753 * <p> 12754 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 12755 * animation callback method which will be called by the RecyclerView depends on the 12756 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 12757 * LayoutManager's decision whether to layout the changed version of a disappearing 12758 * ViewHolder or not. RecyclerView will call 12759 * {@code animateChange} instead of 12760 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12761 * animateDisappearance} if and only if the ItemAnimator returns {@code false} from 12762 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 12763 * LayoutManager lays out a new disappearing view that holds the updated information. 12764 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 12765 * 12766 * @param oldHolder The ViewHolder before the layout is started, might be the same 12767 * instance with newHolder. 12768 * @param newHolder The ViewHolder after the layout is finished, might be the same 12769 * instance with oldHolder. 12770 * @param preLayoutInfo The information that was returned from 12771 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12772 * @param postLayoutInfo The information that was returned from {@link 12773 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 12774 * 12775 * @return true if a later call to {@link #runPendingAnimations()} is requested, 12776 * false otherwise. 12777 */ 12778 public abstract boolean animateChange(@NonNull ViewHolder oldHolder, 12779 @NonNull ViewHolder newHolder, 12780 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 12781 12782 @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { 12783 int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); 12784 if (viewHolder.isInvalid()) { 12785 return FLAG_INVALIDATED; 12786 } 12787 if ((flags & FLAG_INVALIDATED) == 0) { 12788 final int oldPos = viewHolder.getOldPosition(); 12789 final int pos = viewHolder.getAdapterPosition(); 12790 if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) { 12791 flags |= FLAG_MOVED; 12792 } 12793 } 12794 return flags; 12795 } 12796 12797 /** 12798 * Called when there are pending animations waiting to be started. This state 12799 * is governed by the return values from 12800 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12801 * animateAppearance()}, 12802 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12803 * animateChange()} 12804 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12805 * animatePersistence()}, and 12806 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12807 * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be 12808 * called later to start the associated animations. runPendingAnimations() will be scheduled 12809 * to be run on the next frame. 12810 */ 12811 public abstract void runPendingAnimations(); 12812 12813 /** 12814 * Method called when an animation on a view should be ended immediately. 12815 * This could happen when other events, like scrolling, occur, so that 12816 * animating views can be quickly put into their proper end locations. 12817 * Implementations should ensure that any animations running on the item 12818 * are canceled and affected properties are set to their end values. 12819 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 12820 * animation since the animations are effectively done when this method is called. 12821 * 12822 * @param item The item for which an animation should be stopped. 12823 */ 12824 public abstract void endAnimation(@NonNull ViewHolder item); 12825 12826 /** 12827 * Method called when all item animations should be ended immediately. 12828 * This could happen when other events, like scrolling, occur, so that 12829 * animating views can be quickly put into their proper end locations. 12830 * Implementations should ensure that any animations running on any items 12831 * are canceled and affected properties are set to their end values. 12832 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 12833 * animation since the animations are effectively done when this method is called. 12834 */ 12835 public abstract void endAnimations(); 12836 12837 /** 12838 * Method which returns whether there are any item animations currently running. 12839 * This method can be used to determine whether to delay other actions until 12840 * animations end. 12841 * 12842 * @return true if there are any item animations currently running, false otherwise. 12843 */ 12844 public abstract boolean isRunning(); 12845 12846 /** 12847 * Method to be called by subclasses when an animation is finished. 12848 * <p> 12849 * For each call RecyclerView makes to 12850 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12851 * animateAppearance()}, 12852 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12853 * animatePersistence()}, or 12854 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12855 * animateDisappearance()}, there 12856 * should 12857 * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. 12858 * <p> 12859 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12860 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 12861 * and <code>newHolder</code> (if they are not the same instance). 12862 * 12863 * @param viewHolder The ViewHolder whose animation is finished. 12864 * @see #onAnimationFinished(ViewHolder) 12865 */ 12866 public final void dispatchAnimationFinished(@NonNull ViewHolder viewHolder) { 12867 onAnimationFinished(viewHolder); 12868 if (mListener != null) { 12869 mListener.onAnimationFinished(viewHolder); 12870 } 12871 } 12872 12873 /** 12874 * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the 12875 * ItemAnimator. 12876 * 12877 * @param viewHolder The ViewHolder whose animation is finished. There might still be other 12878 * animations running on this ViewHolder. 12879 * @see #dispatchAnimationFinished(ViewHolder) 12880 */ 12881 public void onAnimationFinished(@NonNull ViewHolder viewHolder) { 12882 } 12883 12884 /** 12885 * Method to be called by subclasses when an animation is started. 12886 * <p> 12887 * For each call RecyclerView makes to 12888 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12889 * animateAppearance()}, 12890 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12891 * animatePersistence()}, or 12892 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 12893 * animateDisappearance()}, there should be a matching 12894 * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. 12895 * <p> 12896 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 12897 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 12898 * and <code>newHolder</code> (if they are not the same instance). 12899 * <p> 12900 * If your ItemAnimator decides not to animate a ViewHolder, it should call 12901 * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling 12902 * {@link #dispatchAnimationStarted(ViewHolder)}. 12903 * 12904 * @param viewHolder The ViewHolder whose animation is starting. 12905 * @see #onAnimationStarted(ViewHolder) 12906 */ 12907 public final void dispatchAnimationStarted(@NonNull ViewHolder viewHolder) { 12908 onAnimationStarted(viewHolder); 12909 } 12910 12911 /** 12912 * Called when a new animation is started on the given ViewHolder. 12913 * 12914 * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder 12915 * might already be animating and this might be another animation. 12916 * @see #dispatchAnimationStarted(ViewHolder) 12917 */ 12918 public void onAnimationStarted(@NonNull ViewHolder viewHolder) { 12919 12920 } 12921 12922 /** 12923 * Like {@link #isRunning()}, this method returns whether there are any item 12924 * animations currently running. Additionally, the listener passed in will be called 12925 * when there are no item animations running, either immediately (before the method 12926 * returns) if no animations are currently running, or when the currently running 12927 * animations are {@link #dispatchAnimationsFinished() finished}. 12928 * 12929 * <p>Note that the listener is transient - it is either called immediately and not 12930 * stored at all, or stored only until it is called when running animations 12931 * are finished sometime later.</p> 12932 * 12933 * @param listener A listener to be called immediately if no animations are running 12934 * or later when currently-running animations have finished. A null listener is 12935 * equivalent to calling {@link #isRunning()}. 12936 * @return true if there are any item animations currently running, false otherwise. 12937 */ 12938 public final boolean isRunning(@Nullable ItemAnimatorFinishedListener listener) { 12939 boolean running = isRunning(); 12940 if (listener != null) { 12941 if (!running) { 12942 listener.onAnimationsFinished(); 12943 } else { 12944 mFinishedListeners.add(listener); 12945 } 12946 } 12947 return running; 12948 } 12949 12950 /** 12951 * When an item is changed, ItemAnimator can decide whether it wants to re-use 12952 * the same ViewHolder for animations or RecyclerView should create a copy of the 12953 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 12954 * <p> 12955 * Note that this method will only be called if the {@link ViewHolder} still has the same 12956 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 12957 * both {@link ViewHolder}s in the 12958 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 12959 * <p> 12960 * If your application is using change payloads, you can override 12961 * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. 12962 * 12963 * @param viewHolder The ViewHolder which represents the changed item's old content. 12964 * 12965 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 12966 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 12967 * ItemAnimator to animate. Default implementation returns <code>true</code>. 12968 * 12969 * @see #canReuseUpdatedViewHolder(ViewHolder, List) 12970 */ 12971 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { 12972 return true; 12973 } 12974 12975 /** 12976 * When an item is changed, ItemAnimator can decide whether it wants to re-use 12977 * the same ViewHolder for animations or RecyclerView should create a copy of the 12978 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 12979 * <p> 12980 * Note that this method will only be called if the {@link ViewHolder} still has the same 12981 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 12982 * both {@link ViewHolder}s in the 12983 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 12984 * 12985 * @param viewHolder The ViewHolder which represents the changed item's old content. 12986 * @param payloads A non-null list of merged payloads that were sent with change 12987 * notifications. Can be empty if the adapter is invalidated via 12988 * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of 12989 * payloads will be passed into 12990 * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} 12991 * method <b>if</b> this method returns <code>true</code>. 12992 * 12993 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 12994 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 12995 * ItemAnimator to animate. Default implementation calls 12996 * {@link #canReuseUpdatedViewHolder(ViewHolder)}. 12997 * 12998 * @see #canReuseUpdatedViewHolder(ViewHolder) 12999 */ 13000 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, 13001 @NonNull List<Object> payloads) { 13002 return canReuseUpdatedViewHolder(viewHolder); 13003 } 13004 13005 /** 13006 * This method should be called by ItemAnimator implementations to notify 13007 * any listeners that all pending and active item animations are finished. 13008 */ 13009 public final void dispatchAnimationsFinished() { 13010 final int count = mFinishedListeners.size(); 13011 for (int i = 0; i < count; ++i) { 13012 mFinishedListeners.get(i).onAnimationsFinished(); 13013 } 13014 mFinishedListeners.clear(); 13015 } 13016 13017 /** 13018 * Returns a new {@link ItemHolderInfo} which will be used to store information about the 13019 * ViewHolder. This information will later be passed into <code>animate**</code> methods. 13020 * <p> 13021 * You can override this method if you want to extend {@link ItemHolderInfo} and provide 13022 * your own instances. 13023 * 13024 * @return A new {@link ItemHolderInfo}. 13025 */ 13026 @NonNull 13027 public ItemHolderInfo obtainHolderInfo() { 13028 return new ItemHolderInfo(); 13029 } 13030 13031 /** 13032 * The interface to be implemented by listeners to animation events from this 13033 * ItemAnimator. This is used internally and is not intended for developers to 13034 * create directly. 13035 */ 13036 interface ItemAnimatorListener { 13037 void onAnimationFinished(@NonNull ViewHolder item); 13038 } 13039 13040 /** 13041 * This interface is used to inform listeners when all pending or running animations 13042 * in an ItemAnimator are finished. This can be used, for example, to delay an action 13043 * in a data set until currently-running animations are complete. 13044 * 13045 * @see #isRunning(ItemAnimatorFinishedListener) 13046 */ 13047 public interface ItemAnimatorFinishedListener { 13048 /** 13049 * Notifies when all pending or running animations in an ItemAnimator are finished. 13050 */ 13051 void onAnimationsFinished(); 13052 } 13053 13054 /** 13055 * A simple data structure that holds information about an item's bounds. 13056 * This information is used in calculating item animations. Default implementation of 13057 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and 13058 * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data 13059 * structure. You can extend this class if you would like to keep more information about 13060 * the Views. 13061 * <p> 13062 * If you want to provide your own implementation but still use `super` methods to record 13063 * basic information, you can override {@link #obtainHolderInfo()} to provide your own 13064 * instances. 13065 */ 13066 public static class ItemHolderInfo { 13067 13068 /** 13069 * The left edge of the View (excluding decorations) 13070 */ 13071 public int left; 13072 13073 /** 13074 * The top edge of the View (excluding decorations) 13075 */ 13076 public int top; 13077 13078 /** 13079 * The right edge of the View (excluding decorations) 13080 */ 13081 public int right; 13082 13083 /** 13084 * The bottom edge of the View (excluding decorations) 13085 */ 13086 public int bottom; 13087 13088 /** 13089 * The change flags that were passed to 13090 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. 13091 */ 13092 @AdapterChanges 13093 public int changeFlags; 13094 13095 public ItemHolderInfo() { 13096 } 13097 13098 /** 13099 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 13100 * the given ViewHolder. Clears all {@link #changeFlags}. 13101 * 13102 * @param holder The ViewHolder whose bounds should be copied. 13103 * @return This {@link ItemHolderInfo} 13104 */ 13105 @NonNull 13106 public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder) { 13107 return setFrom(holder, 0); 13108 } 13109 13110 /** 13111 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 13112 * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. 13113 * 13114 * @param holder The ViewHolder whose bounds should be copied. 13115 * @param flags The adapter change flags that were passed into 13116 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, 13117 * List)}. 13118 * @return This {@link ItemHolderInfo} 13119 */ 13120 @NonNull 13121 public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder, 13122 @AdapterChanges int flags) { 13123 final View view = holder.itemView; 13124 this.left = view.getLeft(); 13125 this.top = view.getTop(); 13126 this.right = view.getRight(); 13127 this.bottom = view.getBottom(); 13128 return this; 13129 } 13130 } 13131 } 13132 13133 @Override 13134 protected int getChildDrawingOrder(int childCount, int i) { 13135 if (mChildDrawingOrderCallback == null) { 13136 return super.getChildDrawingOrder(childCount, i); 13137 } else { 13138 return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i); 13139 } 13140 } 13141 13142 /** 13143 * A callback interface that can be used to alter the drawing order of RecyclerView children. 13144 * <p> 13145 * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case 13146 * that applies to that method also applies to this callback. For example, changing the drawing 13147 * order of two views will not have any effect if their elevation values are different since 13148 * elevation overrides the result of this callback. 13149 */ 13150 public interface ChildDrawingOrderCallback { 13151 /** 13152 * Returns the index of the child to draw for this iteration. Override this 13153 * if you want to change the drawing order of children. By default, it 13154 * returns i. 13155 * 13156 * @param i The current iteration. 13157 * @return The index of the child to draw this iteration. 13158 * 13159 * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) 13160 */ 13161 int onGetChildDrawingOrder(int childCount, int i); 13162 } 13163 13164 private NestedScrollingChildHelper getScrollingChildHelper() { 13165 if (mScrollingChildHelper == null) { 13166 mScrollingChildHelper = new NestedScrollingChildHelper(this); 13167 } 13168 return mScrollingChildHelper; 13169 } 13170} 13171