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 18 package androidx.recyclerview.widget; 19 20 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 21 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 22 import static androidx.core.util.Preconditions.checkArgument; 23 import static androidx.core.view.ViewCompat.TYPE_NON_TOUCH; 24 import static androidx.core.view.ViewCompat.TYPE_TOUCH; 25 26 import android.animation.LayoutTransition; 27 import android.annotation.SuppressLint; 28 import android.content.Context; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.database.Observable; 32 import android.graphics.Canvas; 33 import android.graphics.Matrix; 34 import android.graphics.PointF; 35 import android.graphics.Rect; 36 import android.graphics.RectF; 37 import android.graphics.drawable.Drawable; 38 import android.graphics.drawable.StateListDrawable; 39 import android.hardware.SensorManager; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.os.Parcel; 43 import android.os.Parcelable; 44 import android.os.SystemClock; 45 import android.os.Trace; 46 import android.util.AttributeSet; 47 import android.util.Log; 48 import android.util.SparseArray; 49 import android.view.Display; 50 import android.view.FocusFinder; 51 import android.view.InputDevice; 52 import android.view.KeyEvent; 53 import android.view.MotionEvent; 54 import android.view.VelocityTracker; 55 import android.view.View; 56 import android.view.ViewConfiguration; 57 import android.view.ViewGroup; 58 import android.view.ViewParent; 59 import android.view.accessibility.AccessibilityEvent; 60 import android.view.accessibility.AccessibilityManager; 61 import android.view.animation.Interpolator; 62 import android.widget.EdgeEffect; 63 import android.widget.LinearLayout; 64 import android.widget.OverScroller; 65 66 import androidx.annotation.CallSuper; 67 import androidx.annotation.DoNotInline; 68 import androidx.annotation.IntDef; 69 import androidx.annotation.Px; 70 import androidx.annotation.RequiresApi; 71 import androidx.annotation.RestrictTo; 72 import androidx.annotation.VisibleForTesting; 73 import androidx.core.os.TraceCompat; 74 import androidx.core.util.Preconditions; 75 import androidx.core.view.AccessibilityDelegateCompat; 76 import androidx.core.view.DifferentialMotionFlingController; 77 import androidx.core.view.DifferentialMotionFlingTarget; 78 import androidx.core.view.InputDeviceCompat; 79 import androidx.core.view.MotionEventCompat; 80 import androidx.core.view.NestedScrollingChild2; 81 import androidx.core.view.NestedScrollingChild3; 82 import androidx.core.view.NestedScrollingChildHelper; 83 import androidx.core.view.ScrollFeedbackProviderCompat; 84 import androidx.core.view.ScrollingView; 85 import androidx.core.view.ViewCompat; 86 import androidx.core.view.ViewConfigurationCompat; 87 import androidx.core.view.accessibility.AccessibilityEventCompat; 88 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 89 import androidx.core.widget.EdgeEffectCompat; 90 import androidx.customview.poolingcontainer.PoolingContainer; 91 import androidx.customview.poolingcontainer.PoolingContainerListener; 92 import androidx.customview.view.AbsSavedState; 93 import androidx.recyclerview.R; 94 import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo; 95 96 import org.jspecify.annotations.NonNull; 97 import org.jspecify.annotations.Nullable; 98 99 import java.lang.annotation.Retention; 100 import java.lang.annotation.RetentionPolicy; 101 import java.lang.ref.WeakReference; 102 import java.lang.reflect.Constructor; 103 import java.lang.reflect.InvocationTargetException; 104 import java.util.ArrayList; 105 import java.util.Collections; 106 import java.util.IdentityHashMap; 107 import java.util.List; 108 import java.util.Set; 109 110 /** 111 * A flexible view for providing a limited window into a large data set. 112 * 113 * <h3>Glossary of terms:</h3> 114 * 115 * <ul> 116 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 117 * that represent items in a data set.</li> 118 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 119 * <li><em>Index:</em> The index of an attached child view as used in a call to 120 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 121 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 122 * to a <em>position</em> within the adapter.</li> 123 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 124 * position may be placed in a cache for later reuse to display the same type of data again 125 * later. This can drastically improve performance by skipping initial layout inflation 126 * or construction.</li> 127 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 128 * state during layout. Scrap views may be reused without becoming fully detached 129 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 130 * by the adapter if the view was considered <em>dirty</em>.</li> 131 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 132 * being displayed.</li> 133 * </ul> 134 * 135 * <h3>Positions in RecyclerView:</h3> 136 * <p> 137 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and 138 * {@link LayoutManager} to be able to detect data set changes in batches during a layout 139 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. 140 * It also helps with performance because all view bindings happen at the same time and unnecessary 141 * bindings are avoided. 142 * <p> 143 * For this reason, there are two types of <code>position</code> related methods in RecyclerView: 144 * <ul> 145 * <li>layout position: Position of an item in the latest layout calculation. This is the 146 * position from the LayoutManager's perspective.</li> 147 * <li>adapter position: Position of an item in the adapter. This is the position from 148 * the Adapter's perspective.</li> 149 * </ul> 150 * <p> 151 * These two positions are the same except the time between dispatching <code>adapter.notify* 152 * </code> events and calculating the updated layout. 153 * <p> 154 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest 155 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, 156 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the 157 * last layout calculation. You can rely on these positions to be consistent with what user is 158 * currently seeing on the screen. For example, if you have a list of items on the screen and user 159 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user 160 * is seeing. 161 * <p> 162 * The other set of position related methods are in the form of 163 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAbsoluteAdapterPosition()}, 164 * {@link ViewHolder#getBindingAdapterPosition()}, 165 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to 166 * work with up-to-date adapter positions even if they may not have been reflected to layout yet. 167 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use 168 * {@link ViewHolder#getBindingAdapterPosition()}. Beware that these methods may not be able to 169 * calculate adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new 170 * layout has not yet been calculated. For this reasons, you should carefully handle 171 * {@link #NO_POSITION} or <code>null</code> results from these methods. 172 * <p> 173 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when 174 * writing an {@link Adapter}, you probably want to use adapter positions. 175 * <p> 176 * <h3>Presenting Dynamic Data</h3> 177 * To display updatable data in a RecyclerView, your adapter needs to signal inserts, moves, and 178 * deletions to RecyclerView. You can build this yourself by manually calling 179 * {@code adapter.notify*} methods when content changes, or you can use one of the easier solutions 180 * RecyclerView provides: 181 * <p> 182 * <h4>List diffing with DiffUtil</h4> 183 * If your RecyclerView is displaying a list that is re-fetched from scratch for each update (e.g. 184 * from the network, or from a database), {@link DiffUtil} can calculate the difference between 185 * versions of the list. {@code DiffUtil} takes both lists as input and computes the difference, 186 * which can be passed to RecyclerView to trigger minimal animations and updates to keep your UI 187 * performant, and animations meaningful. This approach requires that each list is represented in 188 * memory with immutable content, and relies on receiving updates as new instances of lists. This 189 * approach is also ideal if your UI layer doesn't implement sorting, it just presents the data in 190 * the order it's given. 191 * <p> 192 * The best part of this approach is that it extends to any arbitrary changes - item updates, 193 * moves, addition and removal can all be computed and handled the same way. Though you do have 194 * to keep two copies of the list in memory while diffing, and must avoid mutating them, it's 195 * possible to share unmodified elements between list versions. 196 * <p> 197 * There are three primary ways to do this for RecyclerView. We recommend you start with 198 * {@link ListAdapter}, the higher-level API that builds in {@link List} diffing on a background 199 * thread, with minimal code. {@link AsyncListDiffer} also provides this behavior, but without 200 * defining an Adapter to subclass. If you want more control, {@link DiffUtil} is the lower-level 201 * API you can use to compute the diffs yourself. Each approach allows you to specify how diffs 202 * should be computed based on item data. 203 * <p> 204 * <h4>List mutation with SortedList</h4> 205 * If your RecyclerView receives updates incrementally, e.g. item X is inserted, or item Y is 206 * removed, you can use {@link SortedList} to manage your list. You define how to order items, 207 * and it will automatically trigger update signals that RecyclerView can use. SortedList works 208 * if you only need to handle insert and remove events, and has the benefit that you only ever 209 * need to have a single copy of the list in memory. It can also compute differences with 210 * {@link SortedList#replaceAll(Object[])}, but this method is more limited than the list diffing 211 * behavior above. 212 * <p> 213 * <h4>Paging Library</h4> 214 * The <a href="https://developer.android.com/topic/libraries/architecture/paging/">Paging 215 * library</a> extends the diff-based approach to additionally support paged loading. It provides 216 * the {@link androidx.paging.PagedList} class that operates as a self-loading list, provided a 217 * source of data like a database, or paginated network API. It provides convenient list diffing 218 * support out of the box, similar to {@code ListAdapter} and {@code AsyncListDiffer}. For more 219 * information about the Paging library, see the 220 * <a href="https://developer.android.com/topic/libraries/architecture/paging/">library 221 * documentation</a>. 222 * 223 * {@link androidx.recyclerview.R.attr#layoutManager} 224 */ 225 public class RecyclerView extends ViewGroup implements ScrollingView, 226 NestedScrollingChild2, NestedScrollingChild3 { 227 228 static final String TAG = "RecyclerView"; 229 230 static boolean sDebugAssertionsEnabled = false; 231 static boolean sVerboseLoggingEnabled = false; 232 233 static final boolean VERBOSE_TRACING = false; 234 235 private static final int[] NESTED_SCROLLING_ATTRS = 236 {16843830 /* android.R.attr.nestedScrollingEnabled */}; 237 238 /** 239 * The following are copied from OverScroller to determine how far a fling will go. 240 */ 241 private static final float SCROLL_FRICTION = 0.015f; 242 private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) 243 private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); 244 private final float mPhysicalCoef; 245 246 /** 247 * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if 248 * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by 249 * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler 250 * recursively traverses itemView and invalidates display list for each ViewGroup that matches 251 * this criteria. 252 */ 253 static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 19 254 || Build.VERSION.SDK_INT == 20; 255 /** 256 * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, 257 * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to 258 * 0 when mode is unspecified. 259 */ 260 static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; 261 262 /** 263 * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to 264 * RenderThread but before the next frame begins. We schedule prefetch work in this window. 265 */ 266 static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; 267 268 /** 269 * When flinging the stretch towards scrolling content, it should destretch quicker than the 270 * fling would normally do. The visual effect of flinging the stretch looks strange as little 271 * appears to happen at first and then when the stretch disappears, the content starts 272 * scrolling quickly. 273 */ 274 private static final float FLING_DESTRETCH_FACTOR = 4f; 275 276 /** 277 * A {@link android.content.pm.PackageManager} feature specifying if a device's rotary encoder 278 * has low resolution. Low resolution rotary encoders produce small number of 279 * {@link MotionEvent}s per a 360 degree rotation, meaning that each {@link MotionEvent} has 280 * large scroll values, which make {@link #scrollBy(int, int)} calls feel broken (due to the 281 * fact that each event produces large scrolls, and scrolling with large pixels causes a visible 282 * jump that does not feel smooth). As such, we will try adjusting our handling of generic 283 * motion caused by such low resolution rotary encoders differently to make the scrolling 284 * experience smooth. 285 */ 286 static final String LOW_RES_ROTARY_ENCODER_FEATURE = "android.hardware.rotaryencoder.lowres"; 287 288 static final boolean DISPATCH_TEMP_DETACH = false; 289 290 @RestrictTo(LIBRARY_GROUP_PREFIX) 291 @IntDef({HORIZONTAL, VERTICAL}) 292 @Retention(RetentionPolicy.SOURCE) 293 public @interface Orientation { 294 } 295 296 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 297 public static final int VERTICAL = LinearLayout.VERTICAL; 298 299 static final int DEFAULT_ORIENTATION = VERTICAL; 300 public static final int NO_POSITION = -1; 301 public static final long NO_ID = -1; 302 public static final int INVALID_TYPE = -1; 303 304 /** 305 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 306 * that the RecyclerView should use the standard touch slop for smooth, 307 * continuous scrolling. 308 */ 309 public static final int TOUCH_SLOP_DEFAULT = 0; 310 311 /** 312 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates 313 * that the RecyclerView should use the standard touch slop for scrolling 314 * widgets that snap to a page or other coarse-grained barrier. 315 */ 316 public static final int TOUCH_SLOP_PAGING = 1; 317 318 /** 319 * Constant that represents that a duration has not been defined. 320 */ 321 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; 322 323 static final int MAX_SCROLL_DURATION = 2000; 324 325 /** 326 * RecyclerView is calculating a scroll. 327 * If there are too many of these in Systrace, some Views inside RecyclerView might be causing 328 * it. Try to avoid using EditText, focusable views or handle them with care. 329 */ 330 static final String TRACE_SCROLL_TAG = "RV Scroll"; 331 332 /** 333 * OnLayout has been called by the View system. 334 * If this shows up too many times in Systrace, make sure the children of RecyclerView do not 335 * update themselves directly. This will cause a full re-layout but when it happens via the 336 * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation. 337 */ 338 private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout"; 339 340 /** 341 * NotifyDataSetChanged or equal has been called. 342 * If this is taking a long time, try sending granular notify adapter changes instead of just 343 * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter 344 * might help. 345 */ 346 private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate"; 347 348 /** 349 * RecyclerView is doing a layout for partial adapter updates (we know what has changed) 350 * If this is taking a long time, you may have dispatched too many Adapter updates causing too 351 * many Views being rebind. Make sure all are necessary and also prefer using notify*Range 352 * methods. 353 */ 354 private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate"; 355 356 357 /** 358 * RecyclerView is attempting to pre-populate off screen views. 359 */ 360 static final String TRACE_PREFETCH_TAG = "RV Prefetch"; 361 362 /** 363 * RecyclerView is creating a new View. 364 * If too many of these present in Systrace: 365 * - There might be a problem in Recycling (e.g. custom Animations that set transient state and 366 * prevent recycling or ItemAnimator not implementing the contract properly. ({@link 367 * > Adapter#onFailedToRecycleView(ViewHolder)}) 368 * 369 * - There might be too many item view types. 370 * > Try merging them 371 * 372 * - There might be too many itemChange animations and not enough space in RecyclerPool. 373 * >Try increasing your pool size and item cache size. 374 */ 375 static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; 376 private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = 377 new Class<?>[]{Context.class, AttributeSet.class, int.class, int.class}; 378 379 /** 380 * Enable internal assertions about RecyclerView's state and throw exceptions if the 381 * assertions are violated. 382 * <p> 383 * This is primarily intended to diagnose problems with RecyclerView, and 384 * <strong>should not be enabled in production</strong> unless you have a specific reason to 385 * do so. 386 * <p> 387 * Enabling this may negatively affect performance and/or stability. 388 * 389 * @param debugAssertionsEnabled true to enable assertions; false to disable them 390 */ setDebugAssertionsEnabled(boolean debugAssertionsEnabled)391 public static void setDebugAssertionsEnabled(boolean debugAssertionsEnabled) { 392 RecyclerView.sDebugAssertionsEnabled = debugAssertionsEnabled; 393 } 394 395 /** 396 * Enable verbose logging within RecyclerView itself. 397 * <p> 398 * Enabling this may negatively affect performance and reduce the utility of logcat due to 399 * high-volume logging. This generally <strong>should not be enabled in production</strong> 400 * unless you have a specific reason for doing so. 401 * 402 * @param verboseLoggingEnabled true to enable logging; false to disable it 403 */ setVerboseLoggingEnabled(boolean verboseLoggingEnabled)404 public static void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) { 405 RecyclerView.sVerboseLoggingEnabled = verboseLoggingEnabled; 406 } 407 408 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 409 410 final Recycler mRecycler = new Recycler(); 411 412 SavedState mPendingSavedState; 413 414 /** 415 * Handles adapter updates 416 */ 417 AdapterHelper mAdapterHelper; 418 419 /** 420 * Handles abstraction between LayoutManager children and RecyclerView children 421 */ 422 ChildHelper mChildHelper; 423 424 /** 425 * Keeps data about views to be used for animations 426 */ 427 final ViewInfoStore mViewInfoStore = new ViewInfoStore(); 428 429 /** 430 * Prior to L, there is no way to query this variable which is why we override the setter and 431 * track it here. 432 */ 433 boolean mClipToPadding; 434 435 /** 436 * Note: this Runnable is only ever posted if: 437 * 1) We've been through first layout 438 * 2) We know we have a fixed size (mHasFixedSize) 439 * 3) We're attached 440 */ 441 final Runnable mUpdateChildViewsRunnable = new Runnable() { 442 @Override 443 public void run() { 444 if (!mFirstLayoutComplete || isLayoutRequested()) { 445 // a layout request will happen, we should not do layout here. 446 return; 447 } 448 if (!mIsAttached) { 449 requestLayout(); 450 // if we are not attached yet, mark us as requiring layout and skip 451 return; 452 } 453 if (mLayoutSuppressed) { 454 mLayoutWasDefered = true; 455 return; //we'll process updates when ice age ends. 456 } 457 consumePendingUpdateOperations(); 458 } 459 }; 460 461 final Rect mTempRect = new Rect(); 462 private final Rect mTempRect2 = new Rect(); 463 final RectF mTempRectF = new RectF(); 464 Adapter mAdapter; 465 @VisibleForTesting 466 LayoutManager mLayout; 467 // TODO: Remove this once setRecyclerListener has been removed. 468 RecyclerListener mRecyclerListener; 469 // default access to avoid the need for synthetic accessors for Recycler inner class. 470 final List<RecyclerListener> mRecyclerListeners = new ArrayList<>(); 471 final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>(); 472 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 473 new ArrayList<>(); 474 private OnItemTouchListener mInterceptingOnItemTouchListener; 475 boolean mIsAttached; 476 boolean mHasFixedSize; 477 boolean mEnableFastScroller; 478 @VisibleForTesting 479 boolean mFirstLayoutComplete; 480 481 /** 482 * The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of 483 * calls to {@link #startInterceptRequestLayout()} - number of calls to 484 * {@link #stopInterceptRequestLayout(boolean)} . This is used to signal whether we 485 * should defer layout operations caused by layout requests from children of 486 * {@link RecyclerView}. 487 */ 488 private int mInterceptRequestLayoutDepth = 0; 489 490 /** 491 * True if a call to requestLayout was intercepted and prevented from executing like normal and 492 * we plan on continuing with normal execution later. 493 */ 494 boolean mLayoutWasDefered; 495 496 boolean mLayoutSuppressed; 497 private boolean mIgnoreMotionEventTillDown; 498 499 // binary OR of change events that were eaten during a layout or scroll. 500 private int mEatenAccessibilityChangeFlags; 501 boolean mAdapterUpdateDuringMeasure; 502 503 private final AccessibilityManager mAccessibilityManager; 504 private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners; 505 506 /** 507 * True after an event occurs that signals that the entire data set has changed. In that case, 508 * we cannot run any animations since we don't know what happened until layout. 509 * 510 * Attached items are invalid until next layout, at which point layout will animate/replace 511 * items as necessary, building up content from the (effectively) new adapter from scratch. 512 * 513 * Cached items must be discarded when setting this to true, so that the cache may be freely 514 * used by prefetching until the next layout occurs. 515 * 516 * @see #processDataSetCompletelyChanged(boolean) 517 */ 518 boolean mDataSetHasChangedAfterLayout = false; 519 520 /** 521 * True after the data set has completely changed and 522 * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent 523 * measure/layout. 524 * 525 * @see #processDataSetCompletelyChanged(boolean) 526 */ 527 boolean mDispatchItemsChangedEvent = false; 528 529 /** 530 * This variable is incremented during a dispatchLayout and/or scroll. 531 * Some methods should not be called during these periods (e.g. adapter data change). 532 * Doing so will create hard to find bugs so we better check it and throw an exception. 533 * 534 * @see #assertInLayoutOrScroll(String) 535 * @see #assertNotInLayoutOrScroll(String) 536 */ 537 private int mLayoutOrScrollCounter = 0; 538 539 /** 540 * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception 541 * (for API compatibility). 542 * <p> 543 * It is a bad practice for a developer to update the data in a scroll callback since it is 544 * potentially called during a layout. 545 */ 546 private int mDispatchScrollCounter = 0; 547 548 private @NonNull EdgeEffectFactory mEdgeEffectFactory = sDefaultEdgeEffectFactory; 549 private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 550 551 ItemAnimator mItemAnimator = new DefaultItemAnimator(); 552 553 private static final int INVALID_POINTER = -1; 554 555 /** 556 * The RecyclerView is not currently scrolling. 557 * 558 * @see #getScrollState() 559 */ 560 public static final int SCROLL_STATE_IDLE = 0; 561 562 /** 563 * The RecyclerView is currently being dragged by outside input such as user touch input. 564 * 565 * @see #getScrollState() 566 */ 567 public static final int SCROLL_STATE_DRAGGING = 1; 568 569 /** 570 * The RecyclerView is currently animating to a final position while not under 571 * outside control. 572 * 573 * @see #getScrollState() 574 */ 575 public static final int SCROLL_STATE_SETTLING = 2; 576 577 static final long FOREVER_NS = Long.MAX_VALUE; 578 579 // Touch/scrolling handling 580 581 private int mScrollState = SCROLL_STATE_IDLE; 582 private int mScrollPointerId = INVALID_POINTER; 583 private VelocityTracker mVelocityTracker; 584 private int mInitialTouchX; 585 private int mInitialTouchY; 586 private int mLastTouchX; 587 private int mLastTouchY; 588 private int mTouchSlop; 589 private OnFlingListener mOnFlingListener; 590 private final int mMinFlingVelocity; 591 private final int mMaxFlingVelocity; 592 593 // This value is used when handling rotary encoder generic motion events. 594 float mScaledHorizontalScrollFactor = Float.MIN_VALUE; 595 float mScaledVerticalScrollFactor = Float.MIN_VALUE; 596 597 private boolean mPreserveFocusAfterLayout = true; 598 599 final ViewFlinger mViewFlinger = new ViewFlinger(); 600 601 GapWorker mGapWorker; 602 GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = 603 ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; 604 605 final State mState = new State(); 606 607 private OnScrollListener mScrollListener; 608 private List<OnScrollListener> mScrollListeners; 609 610 // For use in item animations 611 boolean mItemsAddedOrRemoved = false; 612 boolean mItemsChanged = false; 613 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = 614 new ItemAnimatorRestoreListener(); 615 boolean mPostedAnimatorRunner = false; 616 RecyclerViewAccessibilityDelegate mAccessibilityDelegate; 617 private ChildDrawingOrderCallback mChildDrawingOrderCallback; 618 619 // simple array to keep min and max child position during a layout calculation 620 // preserved not to create a new one in each layout pass 621 private final int[] mMinMaxLayoutPositions = new int[2]; 622 623 private NestedScrollingChildHelper mScrollingChildHelper; 624 private final int[] mScrollOffset = new int[2]; 625 private final int[] mNestedOffsets = new int[2]; 626 627 // Reusable int array to be passed to method calls that mutate it in order to "return" two ints. 628 final int[] mReusableIntPair = new int[2]; 629 630 /** 631 * These are views that had their a11y importance changed during a layout. We defer these events 632 * until the end of the layout because a11y service may make sync calls back to the RV while 633 * the View's state is undefined. 634 */ 635 @VisibleForTesting 636 final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList<>(); 637 638 private Runnable mItemAnimatorRunner = new Runnable() { 639 @Override 640 public void run() { 641 if (mItemAnimator != null) { 642 mItemAnimator.runPendingAnimations(); 643 } 644 mPostedAnimatorRunner = false; 645 } 646 }; 647 648 static final Interpolator sQuinticInterpolator = new Interpolator() { 649 @Override 650 public float getInterpolation(float t) { 651 t -= 1.0f; 652 return t * t * t * t * t + 1.0f; 653 } 654 }; 655 656 static final StretchEdgeEffectFactory sDefaultEdgeEffectFactory = 657 new StretchEdgeEffectFactory(); 658 659 // These fields are only used to track whether we need to layout and measure RV children in 660 // onLayout. 661 // 662 // We track this information because there is an optimized path such that when 663 // LayoutManager#isAutoMeasureEnabled() returns true and we are measured with 664 // MeasureSpec.EXACTLY in both dimensions, we skip measuring and layout children till the 665 // layout phase. 666 // 667 // However, there are times when we are first measured with something other than 668 // MeasureSpec.EXACTLY in both dimensions, in which case we measure and layout children during 669 // onMeasure. Then if we are measured again with EXACTLY, and we skip measurement, we will 670 // get laid out with a different size than we were last aware of being measured with. If 671 // that happens and we don't check for it, we may not remeasure children, which would be a bug. 672 // 673 // mLastAutoMeasureNonExactMeasureResult tracks our last known measurements in this case, and 674 // mLastAutoMeasureSkippedDueToExact tracks whether or not we skipped. So, whenever we 675 // layout, we can see if our last known measurement information is different from our actual 676 // laid out size, and if it is, only then do we remeasure and relayout children. 677 private boolean mLastAutoMeasureSkippedDueToExact; 678 private int mLastAutoMeasureNonExactMeasuredWidth = 0; 679 private int mLastAutoMeasureNonExactMeasuredHeight = 0; 680 681 /** 682 * Whether or not the device has {@link #LOW_RES_ROTARY_ENCODER_FEATURE}. Computed once and 683 * cached, since it's a static value that would not change on consecutive calls. 684 */ 685 @VisibleForTesting boolean mLowResRotaryEncoderFeature; 686 687 /** 688 * The callback to convert view info diffs into animations. 689 */ 690 private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = 691 new ViewInfoStore.ProcessCallback() { 692 @Override 693 public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, 694 @Nullable ItemHolderInfo postInfo) { 695 mRecycler.unscrapView(viewHolder); 696 animateDisappearance(viewHolder, info, postInfo); 697 } 698 699 @Override 700 public void processAppeared(ViewHolder viewHolder, 701 ItemHolderInfo preInfo, ItemHolderInfo info) { 702 animateAppearance(viewHolder, preInfo, info); 703 } 704 705 @Override 706 public void processPersistent(ViewHolder viewHolder, 707 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 708 viewHolder.setIsRecyclable(false); 709 if (mDataSetHasChangedAfterLayout) { 710 // since it was rebound, use change instead as we'll be mapping them from 711 // stable ids. If stable ids were false, we would not be running any 712 // animations 713 if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, 714 postInfo)) { 715 postAnimationRunner(); 716 } 717 } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { 718 postAnimationRunner(); 719 } 720 } 721 722 @Override 723 public void unused(ViewHolder viewHolder) { 724 mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); 725 } 726 }; 727 728 private final DifferentialMotionFlingTarget 729 mDifferentialMotionFlingTarget = 730 new DifferentialMotionFlingTarget() { 731 @Override 732 public boolean startDifferentialMotionFling(float velocity) { 733 int vx = 0; 734 int vy = 0; 735 if (mLayout.canScrollVertically()) { 736 vy = (int) velocity; 737 } else if (mLayout.canScrollHorizontally()) { 738 vx = (int) velocity; 739 } 740 741 if (vx == 0 && vy == 0) { 742 return false; 743 } 744 745 stopScroll(); 746 // Fling with no threshold check, since the DifferentialMotionFlingHelper should 747 // have handled this already. 748 return flingNoThresholdCheck(vx, vy); 749 } 750 751 @Override 752 public void stopDifferentialMotionFling() { 753 stopScroll(); 754 } 755 756 @Override 757 public float getScaledScrollFactor() { 758 if (mLayout.canScrollVertically()) { 759 return -mScaledVerticalScrollFactor; 760 } 761 if (mLayout.canScrollHorizontally()) { 762 return -mScaledHorizontalScrollFactor; 763 } 764 return 0; 765 } 766 767 }; 768 769 @VisibleForTesting 770 DifferentialMotionFlingController mDifferentialMotionFlingController = 771 new DifferentialMotionFlingController(getContext(), mDifferentialMotionFlingTarget); 772 773 @VisibleForTesting 774 @Nullable 775 ScrollFeedbackProviderCompat mScrollFeedbackProvider; 776 RecyclerView(@onNull Context context)777 public RecyclerView(@NonNull Context context) { 778 this(context, null); 779 } 780 RecyclerView(@onNull Context context, @Nullable AttributeSet attrs)781 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 782 this(context, attrs, R.attr.recyclerViewStyle); 783 } 784 RecyclerView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)785 public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 786 super(context, attrs, defStyleAttr); 787 setScrollContainer(true); 788 setFocusableInTouchMode(true); 789 790 final ViewConfiguration vc = ViewConfiguration.get(context); 791 mTouchSlop = vc.getScaledTouchSlop(); 792 mScaledHorizontalScrollFactor = 793 ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); 794 mScaledVerticalScrollFactor = 795 ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); 796 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 797 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 798 final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; 799 mPhysicalCoef = SensorManager.GRAVITY_EARTH // g (m/s^2) 800 * 39.37f // inch/meter 801 * ppi 802 * 0.84f; // look and feel tuning 803 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 804 805 mItemAnimator.setListener(mItemAnimatorListener); 806 initAdapterManager(); 807 initChildrenHelper(); 808 initAutofill(); 809 // If not explicitly specified this view is important for accessibility. 810 if (this.getImportantForAccessibility() 811 == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 812 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 813 } 814 mAccessibilityManager = (AccessibilityManager) getContext() 815 .getSystemService(Context.ACCESSIBILITY_SERVICE); 816 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); 817 818 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 819 defStyleAttr, 0); 820 821 ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.RecyclerView, 822 attrs, a, defStyleAttr, 0); 823 String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); 824 int descendantFocusability = a.getInt( 825 R.styleable.RecyclerView_android_descendantFocusability, -1); 826 if (descendantFocusability == -1) { 827 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 828 } 829 mClipToPadding = a.getBoolean(R.styleable.RecyclerView_android_clipToPadding, true); 830 mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); 831 if (mEnableFastScroller) { 832 StateListDrawable verticalThumbDrawable = (StateListDrawable) a 833 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); 834 Drawable verticalTrackDrawable = a 835 .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); 836 StateListDrawable horizontalThumbDrawable = (StateListDrawable) a 837 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); 838 Drawable horizontalTrackDrawable = a 839 .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); 840 initFastScroller(verticalThumbDrawable, verticalTrackDrawable, 841 horizontalThumbDrawable, horizontalTrackDrawable); 842 } 843 a.recycle(); 844 845 mLowResRotaryEncoderFeature = 846 context.getPackageManager().hasSystemFeature(LOW_RES_ROTARY_ENCODER_FEATURE); 847 848 // Create the layoutManager if specified. 849 createLayoutManager(context, layoutManagerName, attrs, defStyleAttr, 0); 850 851 boolean nestedScrollingEnabled = true; 852 if (Build.VERSION.SDK_INT >= 21) { 853 a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, 854 defStyleAttr, 0); 855 ViewCompat.saveAttributeDataForStyleable(this, 856 context, NESTED_SCROLLING_ATTRS, attrs, a, defStyleAttr, 0); 857 nestedScrollingEnabled = a.getBoolean(0, true); 858 a.recycle(); 859 } 860 // Re-set whether nested scrolling is enabled so that it is set on all API levels 861 setNestedScrollingEnabled(nestedScrollingEnabled); 862 PoolingContainer.setPoolingContainer(this, true); 863 } 864 865 /** 866 * Label appended to all public exception strings, used to help find which RV in an app is 867 * hitting an exception. 868 */ exceptionLabel()869 String exceptionLabel() { 870 return " " + super.toString() 871 + ", adapter:" + mAdapter 872 + ", layout:" + mLayout 873 + ", context:" + getContext(); 874 } 875 876 /** 877 * If not explicitly specified, this view and its children don't support autofill. 878 * <p> 879 * This is done because autofill's means of uniquely identifying views doesn't work out of the 880 * box with View recycling. 881 */ 882 @SuppressLint("InlinedApi") initAutofill()883 private void initAutofill() { 884 if (ViewCompat.getImportantForAutofill(this) == View.IMPORTANT_FOR_AUTOFILL_AUTO) { 885 ViewCompat.setImportantForAutofill(this, 886 View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS); 887 } 888 } 889 890 /** 891 * Returns the accessibility delegate compatibility implementation used by the RecyclerView. 892 * 893 * @return An instance of AccessibilityDelegateCompat used by RecyclerView 894 */ getCompatAccessibilityDelegate()895 public @Nullable RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { 896 return mAccessibilityDelegate; 897 } 898 899 /** 900 * Sets the accessibility delegate compatibility implementation used by RecyclerView. 901 * 902 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. 903 */ setAccessibilityDelegateCompat( @ullable RecyclerViewAccessibilityDelegate accessibilityDelegate)904 public void setAccessibilityDelegateCompat( 905 @Nullable RecyclerViewAccessibilityDelegate accessibilityDelegate) { 906 mAccessibilityDelegate = accessibilityDelegate; 907 ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); 908 } 909 910 @Override getAccessibilityClassName()911 public CharSequence getAccessibilityClassName() { 912 return "androidx.recyclerview.widget.RecyclerView"; 913 } 914 915 /** 916 * Instantiate and set a LayoutManager, if specified in the attributes. 917 */ createLayoutManager(Context context, String className, AttributeSet attrs, int defStyleAttr, int defStyleRes)918 private void createLayoutManager(Context context, String className, AttributeSet attrs, 919 int defStyleAttr, int defStyleRes) { 920 if (className != null) { 921 className = className.trim(); 922 if (!className.isEmpty()) { 923 className = getFullClassName(context, className); 924 try { 925 ClassLoader classLoader; 926 if (isInEditMode()) { 927 // Stupid layoutlib cannot handle simple class loaders. 928 classLoader = this.getClass().getClassLoader(); 929 } else { 930 classLoader = context.getClassLoader(); 931 } 932 Class<? extends LayoutManager> layoutManagerClass = 933 Class.forName(className, false, classLoader) 934 .asSubclass(LayoutManager.class); 935 Constructor<? extends LayoutManager> constructor; 936 Object[] constructorArgs = null; 937 try { 938 constructor = layoutManagerClass 939 .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE); 940 constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes}; 941 } catch (NoSuchMethodException e) { 942 try { 943 constructor = layoutManagerClass.getConstructor(); 944 } catch (NoSuchMethodException e1) { 945 e1.initCause(e); 946 throw new IllegalStateException(attrs.getPositionDescription() 947 + ": Error creating LayoutManager " + className, e1); 948 } 949 } 950 constructor.setAccessible(true); 951 setLayoutManager(constructor.newInstance(constructorArgs)); 952 } catch (ClassNotFoundException e) { 953 throw new IllegalStateException(attrs.getPositionDescription() 954 + ": Unable to find LayoutManager " + className, e); 955 } catch (InvocationTargetException e) { 956 throw new IllegalStateException(attrs.getPositionDescription() 957 + ": Could not instantiate the LayoutManager: " + className, e); 958 } catch (InstantiationException e) { 959 throw new IllegalStateException(attrs.getPositionDescription() 960 + ": Could not instantiate the LayoutManager: " + className, e); 961 } catch (IllegalAccessException e) { 962 throw new IllegalStateException(attrs.getPositionDescription() 963 + ": Cannot access non-public constructor " + className, e); 964 } catch (ClassCastException e) { 965 throw new IllegalStateException(attrs.getPositionDescription() 966 + ": Class is not a LayoutManager " + className, e); 967 } 968 } 969 } 970 } 971 getFullClassName(Context context, String className)972 private String getFullClassName(Context context, String className) { 973 if (className.charAt(0) == '.') { 974 return context.getPackageName() + className; 975 } 976 if (className.contains(".")) { 977 return className; 978 } 979 return RecyclerView.class.getPackage().getName() + '.' + className; 980 } 981 initChildrenHelper()982 private void initChildrenHelper() { 983 mChildHelper = new ChildHelper(new ChildHelper.Callback() { 984 @Override 985 public int getChildCount() { 986 return RecyclerView.this.getChildCount(); 987 } 988 989 @Override 990 public void addView(View child, int index) { 991 if (VERBOSE_TRACING) { 992 Trace.beginSection("RV addView"); 993 } 994 RecyclerView.this.addView(child, index); 995 if (VERBOSE_TRACING) { 996 Trace.endSection(); 997 } 998 dispatchChildAttached(child); 999 } 1000 1001 @Override 1002 public int indexOfChild(View view) { 1003 return RecyclerView.this.indexOfChild(view); 1004 } 1005 1006 @Override 1007 public void removeViewAt(int index) { 1008 final View child = RecyclerView.this.getChildAt(index); 1009 if (child != null) { 1010 dispatchChildDetached(child); 1011 1012 // Clear any android.view.animation.Animation that may prevent the item from 1013 // detaching when being removed. If a child is re-added before the 1014 // lazy detach occurs, it will receive invalid attach/detach sequencing. 1015 child.clearAnimation(); 1016 } 1017 if (VERBOSE_TRACING) { 1018 Trace.beginSection("RV removeViewAt"); 1019 } 1020 RecyclerView.this.removeViewAt(index); 1021 if (VERBOSE_TRACING) { 1022 Trace.endSection(); 1023 } 1024 } 1025 1026 @Override 1027 public View getChildAt(int offset) { 1028 return RecyclerView.this.getChildAt(offset); 1029 } 1030 1031 @Override 1032 public void removeAllViews() { 1033 final int count = getChildCount(); 1034 for (int i = 0; i < count; i++) { 1035 View child = getChildAt(i); 1036 dispatchChildDetached(child); 1037 1038 // Clear any android.view.animation.Animation that may prevent the item from 1039 // detaching when being removed. If a child is re-added before the 1040 // lazy detach occurs, it will receive invalid attach/detach sequencing. 1041 child.clearAnimation(); 1042 } 1043 RecyclerView.this.removeAllViews(); 1044 } 1045 1046 @Override 1047 public ViewHolder getChildViewHolder(View view) { 1048 return getChildViewHolderInt(view); 1049 } 1050 1051 @Override 1052 public void attachViewToParent(View child, int index, 1053 ViewGroup.LayoutParams layoutParams) { 1054 final ViewHolder vh = getChildViewHolderInt(child); 1055 if (vh != null) { 1056 if (!vh.isTmpDetached() && !vh.shouldIgnore()) { 1057 throw new IllegalArgumentException("Called attach on a child which is not" 1058 + " detached: " + vh + exceptionLabel()); 1059 } 1060 if (sVerboseLoggingEnabled) { 1061 Log.d(TAG, "reAttach " + vh); 1062 } 1063 vh.clearTmpDetachFlag(); 1064 } else { 1065 if (sDebugAssertionsEnabled) { 1066 throw new IllegalArgumentException( 1067 "No ViewHolder found for child: " + child + ", index: " + index 1068 + exceptionLabel()); 1069 } 1070 } 1071 RecyclerView.this.attachViewToParent(child, index, layoutParams); 1072 } 1073 1074 @Override 1075 public void detachViewFromParent(int offset) { 1076 final View view = getChildAt(offset); 1077 if (view != null) { 1078 final ViewHolder vh = getChildViewHolderInt(view); 1079 if (vh != null) { 1080 if (vh.isTmpDetached() && !vh.shouldIgnore()) { 1081 throw new IllegalArgumentException("called detach on an already" 1082 + " detached child " + vh + exceptionLabel()); 1083 } 1084 if (sVerboseLoggingEnabled) { 1085 Log.d(TAG, "tmpDetach " + vh); 1086 } 1087 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); 1088 } 1089 } else { 1090 if (sDebugAssertionsEnabled) { 1091 throw new IllegalArgumentException( 1092 "No view at offset " + offset + exceptionLabel()); 1093 } 1094 } 1095 RecyclerView.this.detachViewFromParent(offset); 1096 } 1097 1098 @Override 1099 public void onEnteredHiddenState(View child) { 1100 final ViewHolder vh = getChildViewHolderInt(child); 1101 if (vh != null) { 1102 vh.onEnteredHiddenState(RecyclerView.this); 1103 } 1104 } 1105 1106 @Override 1107 public void onLeftHiddenState(View child) { 1108 final ViewHolder vh = getChildViewHolderInt(child); 1109 if (vh != null) { 1110 vh.onLeftHiddenState(RecyclerView.this); 1111 } 1112 } 1113 }); 1114 } 1115 initAdapterManager()1116 void initAdapterManager() { 1117 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { 1118 @Override 1119 public ViewHolder findViewHolder(int position) { 1120 final ViewHolder vh = findViewHolderForPosition(position, true); 1121 if (vh == null) { 1122 return null; 1123 } 1124 // ensure it is not hidden because for adapter helper, the only thing matter is that 1125 // LM thinks view is a child. 1126 if (mChildHelper.isHidden(vh.itemView)) { 1127 if (sVerboseLoggingEnabled) { 1128 Log.d(TAG, "assuming view holder cannot be find because it is hidden"); 1129 } 1130 return null; 1131 } 1132 return vh; 1133 } 1134 1135 @Override 1136 public void offsetPositionsForRemovingInvisible(int start, int count) { 1137 offsetPositionRecordsForRemove(start, count, true); 1138 mItemsAddedOrRemoved = true; 1139 mState.mDeletedInvisibleItemCountSincePreviousLayout += count; 1140 } 1141 1142 @Override 1143 public void offsetPositionsForRemovingLaidOutOrNewView( 1144 int positionStart, int itemCount) { 1145 offsetPositionRecordsForRemove(positionStart, itemCount, false); 1146 mItemsAddedOrRemoved = true; 1147 } 1148 1149 1150 @Override 1151 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { 1152 viewRangeUpdate(positionStart, itemCount, payload); 1153 mItemsChanged = true; 1154 } 1155 1156 @Override 1157 public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { 1158 dispatchUpdate(op); 1159 } 1160 1161 void dispatchUpdate(AdapterHelper.UpdateOp op) { 1162 switch (op.cmd) { 1163 case AdapterHelper.UpdateOp.ADD: 1164 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); 1165 break; 1166 case AdapterHelper.UpdateOp.REMOVE: 1167 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); 1168 break; 1169 case AdapterHelper.UpdateOp.UPDATE: 1170 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, 1171 op.payload); 1172 break; 1173 case AdapterHelper.UpdateOp.MOVE: 1174 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); 1175 break; 1176 } 1177 } 1178 1179 @Override 1180 public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { 1181 dispatchUpdate(op); 1182 } 1183 1184 @Override 1185 public void offsetPositionsForAdd(int positionStart, int itemCount) { 1186 offsetPositionRecordsForInsert(positionStart, itemCount); 1187 mItemsAddedOrRemoved = true; 1188 } 1189 1190 @Override 1191 public void offsetPositionsForMove(int from, int to) { 1192 offsetPositionRecordsForMove(from, to); 1193 // should we create mItemsMoved ? 1194 mItemsAddedOrRemoved = true; 1195 } 1196 }); 1197 } 1198 1199 /** 1200 * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's 1201 * size is not affected by the adapter contents. RecyclerView can still change its size based 1202 * on other factors (e.g. its parent's size) but this size calculation cannot depend on the 1203 * size of its children or contents of its adapter (except the number of items in the adapter). 1204 * <p> 1205 * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow 1206 * RecyclerView to avoid invalidating the whole layout when its adapter contents change. 1207 * 1208 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 1209 */ setHasFixedSize(boolean hasFixedSize)1210 public void setHasFixedSize(boolean hasFixedSize) { 1211 mHasFixedSize = hasFixedSize; 1212 } 1213 1214 /** 1215 * @return true if the app has specified that changes in adapter content cannot change 1216 * the size of the RecyclerView itself. 1217 */ hasFixedSize()1218 public boolean hasFixedSize() { 1219 return mHasFixedSize; 1220 } 1221 1222 @Override setClipToPadding(boolean clipToPadding)1223 public void setClipToPadding(boolean clipToPadding) { 1224 if (clipToPadding != mClipToPadding) { 1225 invalidateGlows(); 1226 } 1227 mClipToPadding = clipToPadding; 1228 super.setClipToPadding(clipToPadding); 1229 if (mFirstLayoutComplete) { 1230 requestLayout(); 1231 } 1232 } 1233 1234 /** 1235 * Returns whether this RecyclerView will clip its children to its padding, and resize (but 1236 * not clip) any EdgeEffect to the padded region, if padding is present. 1237 * <p> 1238 * By default, children are clipped to the padding of their parent 1239 * RecyclerView. This clipping behavior is only enabled if padding is non-zero. 1240 * 1241 * @return true if this RecyclerView clips children to its padding and resizes (but doesn't 1242 * clip) any EdgeEffect to the padded region, false otherwise. 1243 * @attr name android:clipToPadding 1244 */ 1245 @Override getClipToPadding()1246 public boolean getClipToPadding() { 1247 return mClipToPadding; 1248 } 1249 1250 /** 1251 * Configure the scrolling touch slop for a specific use case. 1252 * 1253 * Set up the RecyclerView's scrolling motion threshold based on common usages. 1254 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. 1255 * 1256 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing 1257 * the intended usage of this RecyclerView 1258 */ setScrollingTouchSlop(int slopConstant)1259 public void setScrollingTouchSlop(int slopConstant) { 1260 final ViewConfiguration vc = ViewConfiguration.get(getContext()); 1261 switch (slopConstant) { 1262 default: 1263 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " 1264 + slopConstant + "; using default value"); 1265 // fall-through 1266 case TOUCH_SLOP_DEFAULT: 1267 mTouchSlop = vc.getScaledTouchSlop(); 1268 break; 1269 1270 case TOUCH_SLOP_PAGING: 1271 mTouchSlop = vc.getScaledPagingTouchSlop(); 1272 break; 1273 } 1274 } 1275 1276 /** 1277 * Swaps the current adapter with the provided one. It is similar to 1278 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same 1279 * {@link ViewHolder} and does not clear the RecycledViewPool. 1280 * <p> 1281 * Note that it still calls onAdapterChanged callbacks. 1282 * 1283 * @param adapter The new adapter to set, or null to set no adapter. 1284 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing 1285 * Views. If adapters have stable ids and/or you want to 1286 * animate the disappearing views, you may prefer to set 1287 * this to false. 1288 * @see #setAdapter(Adapter) 1289 */ swapAdapter(@ullable Adapter adapter, boolean removeAndRecycleExistingViews)1290 public void swapAdapter(@Nullable Adapter adapter, boolean removeAndRecycleExistingViews) { 1291 // bail out if layout is frozen 1292 setLayoutFrozen(false); 1293 setAdapterInternal(adapter, true, removeAndRecycleExistingViews); 1294 processDataSetCompletelyChanged(true); 1295 requestLayout(); 1296 } 1297 1298 /** 1299 * Set a new adapter to provide child views on demand. 1300 * <p> 1301 * When adapter is changed, all existing views are recycled back to the pool. If the pool has 1302 * only one adapter, it will be cleared. 1303 * 1304 * @param adapter The new adapter to set, or null to set no adapter. 1305 * @see #swapAdapter(Adapter, boolean) 1306 */ setAdapter(@ullable Adapter adapter)1307 public void setAdapter(@Nullable Adapter adapter) { 1308 // bail out if layout is frozen 1309 setLayoutFrozen(false); 1310 setAdapterInternal(adapter, false, true); 1311 processDataSetCompletelyChanged(false); 1312 requestLayout(); 1313 } 1314 1315 /** 1316 * Removes and recycles all views - both those currently attached, and those in the Recycler. 1317 */ removeAndRecycleViews()1318 void removeAndRecycleViews() { 1319 // end all running animations 1320 if (mItemAnimator != null) { 1321 mItemAnimator.endAnimations(); 1322 } 1323 // Since animations are ended, mLayout.children should be equal to 1324 // recyclerView.children. This may not be true if item animator's end does not work as 1325 // expected. (e.g. not release children instantly). It is safer to use mLayout's child 1326 // count. 1327 if (mLayout != null) { 1328 mLayout.removeAndRecycleAllViews(mRecycler); 1329 mLayout.removeAndRecycleScrapInt(mRecycler); 1330 } 1331 // we should clear it here before adapters are swapped to ensure correct callbacks. 1332 mRecycler.clear(); 1333 } 1334 1335 /** 1336 * Replaces the current adapter with the new one and triggers listeners. 1337 * 1338 * @param adapter The new adapter 1339 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and 1340 * item types with the current adapter (helps us avoid cache 1341 * invalidation). 1342 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If 1343 * compatibleWithPrevious is false, this parameter is ignored. 1344 */ setAdapterInternal(@ullable Adapter<?> adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews)1345 private void setAdapterInternal(@Nullable Adapter<?> adapter, boolean compatibleWithPrevious, 1346 boolean removeAndRecycleViews) { 1347 if (mAdapter != null) { 1348 mAdapter.unregisterAdapterDataObserver(mObserver); 1349 mAdapter.onDetachedFromRecyclerView(this); 1350 } 1351 if (!compatibleWithPrevious || removeAndRecycleViews) { 1352 removeAndRecycleViews(); 1353 } 1354 mAdapterHelper.reset(); 1355 final Adapter<?> oldAdapter = mAdapter; 1356 mAdapter = adapter; 1357 if (adapter != null) { 1358 adapter.registerAdapterDataObserver(mObserver); 1359 adapter.onAttachedToRecyclerView(this); 1360 } 1361 if (mLayout != null) { 1362 mLayout.onAdapterChanged(oldAdapter, mAdapter); 1363 } 1364 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 1365 mState.mStructureChanged = true; 1366 } 1367 1368 /** 1369 * Retrieves the previously set adapter or null if no adapter is set. 1370 * 1371 * @return The previously set adapter 1372 * @see #setAdapter(Adapter) 1373 */ getAdapter()1374 public @Nullable Adapter getAdapter() { 1375 return mAdapter; 1376 } 1377 1378 /** 1379 * Register a listener that will be notified whenever a child view is recycled. 1380 * 1381 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1382 * that a child view is no longer needed. If an application associates expensive 1383 * or heavyweight data with item views, this may be a good place to release 1384 * or free those resources.</p> 1385 * 1386 * @param listener Listener to register, or null to clear 1387 * @deprecated Use {@link #addRecyclerListener(RecyclerListener)} and 1388 * {@link #removeRecyclerListener(RecyclerListener)} 1389 */ 1390 @Deprecated setRecyclerListener(@ullable RecyclerListener listener)1391 public void setRecyclerListener(@Nullable RecyclerListener listener) { 1392 mRecyclerListener = listener; 1393 } 1394 1395 /** 1396 * Register a listener that will be notified whenever a child view is recycled. 1397 * 1398 * <p>The listeners will be called when a LayoutManager or the RecyclerView decides 1399 * that a child view is no longer needed. If an application associates data with 1400 * the item views being recycled, this may be a good place to release 1401 * or free those resources.</p> 1402 * 1403 * @param listener Listener to register. 1404 */ addRecyclerListener(@onNull RecyclerListener listener)1405 public void addRecyclerListener(@NonNull RecyclerListener listener) { 1406 checkArgument(listener != null, "'listener' arg cannot " 1407 + "be null."); 1408 mRecyclerListeners.add(listener); 1409 } 1410 1411 /** 1412 * Removes the provided listener from RecyclerListener list. 1413 * 1414 * @param listener Listener to unregister. 1415 */ removeRecyclerListener(@onNull RecyclerListener listener)1416 public void removeRecyclerListener(@NonNull RecyclerListener listener) { 1417 mRecyclerListeners.remove(listener); 1418 } 1419 1420 /** 1421 * <p>Return the offset of the RecyclerView's text baseline from the its top 1422 * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, 1423 * this method returns -1.</p> 1424 * 1425 * @return the offset of the baseline within the RecyclerView's bounds or -1 1426 * if baseline alignment is not supported 1427 */ 1428 @Override getBaseline()1429 public int getBaseline() { 1430 if (mLayout != null) { 1431 return mLayout.getBaseline(); 1432 } else { 1433 return super.getBaseline(); 1434 } 1435 } 1436 1437 /** 1438 * Register a listener that will be notified whenever a child view is attached to or detached 1439 * from RecyclerView. 1440 * 1441 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 1442 * that a child view is no longer needed. If an application associates expensive 1443 * or heavyweight data with item views, this may be a good place to release 1444 * or free those resources.</p> 1445 * 1446 * @param listener Listener to register 1447 */ addOnChildAttachStateChangeListener( @onNull OnChildAttachStateChangeListener listener)1448 public void addOnChildAttachStateChangeListener( 1449 @NonNull OnChildAttachStateChangeListener listener) { 1450 if (mOnChildAttachStateListeners == null) { 1451 mOnChildAttachStateListeners = new ArrayList<>(); 1452 } 1453 mOnChildAttachStateListeners.add(listener); 1454 } 1455 1456 /** 1457 * Removes the provided listener from child attached state listeners list. 1458 * 1459 * @param listener Listener to unregister 1460 */ removeOnChildAttachStateChangeListener( @onNull OnChildAttachStateChangeListener listener)1461 public void removeOnChildAttachStateChangeListener( 1462 @NonNull OnChildAttachStateChangeListener listener) { 1463 if (mOnChildAttachStateListeners == null) { 1464 return; 1465 } 1466 mOnChildAttachStateListeners.remove(listener); 1467 } 1468 1469 /** 1470 * Removes all listeners that were added via 1471 * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}. 1472 */ clearOnChildAttachStateChangeListeners()1473 public void clearOnChildAttachStateChangeListeners() { 1474 if (mOnChildAttachStateListeners != null) { 1475 mOnChildAttachStateListeners.clear(); 1476 } 1477 } 1478 1479 /** 1480 * Set the {@link LayoutManager} that this RecyclerView will use. 1481 * 1482 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 1483 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 1484 * layout arrangements for child views. These arrangements are controlled by the 1485 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 1486 * 1487 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 1488 * 1489 * @param layout LayoutManager to use 1490 */ setLayoutManager(@ullable LayoutManager layout)1491 public void setLayoutManager(@Nullable LayoutManager layout) { 1492 if (layout == mLayout) { 1493 return; 1494 } 1495 stopScroll(); 1496 // TODO We should do this switch a dispatchLayout pass and animate children. There is a good 1497 // chance that LayoutManagers will re-use views. 1498 if (mLayout != null) { 1499 // end all running animations 1500 if (mItemAnimator != null) { 1501 mItemAnimator.endAnimations(); 1502 } 1503 mLayout.removeAndRecycleAllViews(mRecycler); 1504 mLayout.removeAndRecycleScrapInt(mRecycler); 1505 mRecycler.clear(); 1506 1507 if (mIsAttached) { 1508 mLayout.dispatchDetachedFromWindow(this, mRecycler); 1509 } 1510 mLayout.setRecyclerView(null); 1511 mLayout = null; 1512 } else { 1513 mRecycler.clear(); 1514 } 1515 // this is just a defensive measure for faulty item animators. 1516 mChildHelper.removeAllViewsUnfiltered(); 1517 mLayout = layout; 1518 if (layout != null) { 1519 if (layout.mRecyclerView != null) { 1520 throw new IllegalArgumentException("LayoutManager " + layout 1521 + " is already attached to a RecyclerView:" 1522 + layout.mRecyclerView.exceptionLabel()); 1523 } 1524 mLayout.setRecyclerView(this); 1525 if (mIsAttached) { 1526 mLayout.dispatchAttachedToWindow(this); 1527 } 1528 } 1529 mRecycler.updateViewCacheSize(); 1530 requestLayout(); 1531 } 1532 1533 /** 1534 * Set a {@link OnFlingListener} for this {@link RecyclerView}. 1535 * <p> 1536 * If the {@link OnFlingListener} is set then it will receive 1537 * calls to {@link #fling(int, int)} and will be able to intercept them. 1538 * 1539 * @param onFlingListener The {@link OnFlingListener} instance. 1540 */ setOnFlingListener(@ullable OnFlingListener onFlingListener)1541 public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { 1542 mOnFlingListener = onFlingListener; 1543 } 1544 1545 /** 1546 * Get the current {@link OnFlingListener} from this {@link RecyclerView}. 1547 * 1548 * @return The {@link OnFlingListener} instance currently set (can be null). 1549 */ getOnFlingListener()1550 public @Nullable OnFlingListener getOnFlingListener() { 1551 return mOnFlingListener; 1552 } 1553 1554 @Override onSaveInstanceState()1555 protected Parcelable onSaveInstanceState() { 1556 SavedState state = new SavedState(super.onSaveInstanceState()); 1557 if (mPendingSavedState != null) { 1558 state.copyFrom(mPendingSavedState); 1559 } else if (mLayout != null) { 1560 state.mLayoutState = mLayout.onSaveInstanceState(); 1561 } else { 1562 state.mLayoutState = null; 1563 } 1564 1565 return state; 1566 } 1567 1568 @Override onRestoreInstanceState(Parcelable state)1569 protected void onRestoreInstanceState(Parcelable state) { 1570 if (!(state instanceof SavedState)) { 1571 super.onRestoreInstanceState(state); 1572 return; 1573 } 1574 1575 mPendingSavedState = (SavedState) state; 1576 super.onRestoreInstanceState(mPendingSavedState.getSuperState()); 1577 // Historically, some app developers have used onRestoreInstanceState(State) in ways it 1578 // was never intended. For example, some devs have used it to manually set a state they 1579 // updated themselves such that passing the state here would cause a LayoutManager to 1580 // receive it and update its internal state accordingly, even if state was already 1581 // previously restored. Therefore, it is necessary to always call requestLayout to retain 1582 // the functionality even if it otherwise seems like a strange thing to do. 1583 // ¯\_(ツ)_/¯ 1584 requestLayout(); 1585 } 1586 1587 /** 1588 * Override to prevent freezing of any views created by the adapter. 1589 */ 1590 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)1591 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 1592 dispatchFreezeSelfOnly(container); 1593 } 1594 1595 /** 1596 * Override to prevent thawing of any views created by the adapter. 1597 */ 1598 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)1599 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 1600 dispatchThawSelfOnly(container); 1601 } 1602 1603 /** 1604 * Adds a view to the animatingViews list. 1605 * mAnimatingViews holds the child views that are currently being kept around 1606 * purely for the purpose of being animated out of view. They are drawn as a regular 1607 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager 1608 * as they are managed separately from the regular child views. 1609 * 1610 * @param viewHolder The ViewHolder to be removed 1611 */ addAnimatingView(ViewHolder viewHolder)1612 private void addAnimatingView(ViewHolder viewHolder) { 1613 final View view = viewHolder.itemView; 1614 final boolean alreadyParented = view.getParent() == this; 1615 mRecycler.unscrapView(getChildViewHolder(view)); 1616 if (viewHolder.isTmpDetached()) { 1617 // re-attach 1618 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); 1619 } else if (!alreadyParented) { 1620 mChildHelper.addView(view, true); 1621 } else { 1622 mChildHelper.hide(view); 1623 } 1624 } 1625 1626 /** 1627 * Removes a view from the animatingViews list. 1628 * 1629 * @param view The view to be removed 1630 * @return true if an animating view is removed 1631 * @see #addAnimatingView(RecyclerView.ViewHolder) 1632 */ removeAnimatingView(View view)1633 boolean removeAnimatingView(View view) { 1634 startInterceptRequestLayout(); 1635 final boolean removed = mChildHelper.removeViewIfHidden(view); 1636 if (removed) { 1637 final ViewHolder viewHolder = getChildViewHolderInt(view); 1638 mRecycler.unscrapView(viewHolder); 1639 mRecycler.recycleViewHolderInternal(viewHolder); 1640 if (sVerboseLoggingEnabled) { 1641 Log.d(TAG, "after removing animated view: " + view + ", " + this); 1642 } 1643 } 1644 // only clear request eaten flag if we removed the view. 1645 stopInterceptRequestLayout(!removed); 1646 return removed; 1647 } 1648 1649 /** 1650 * Return the {@link LayoutManager} currently responsible for 1651 * layout policy for this RecyclerView. 1652 * 1653 * @return The currently bound LayoutManager 1654 */ getLayoutManager()1655 public @Nullable LayoutManager getLayoutManager() { 1656 return mLayout; 1657 } 1658 1659 /** 1660 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 1661 * if no pool is set for this view a new one will be created. See 1662 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 1663 * 1664 * @return The pool used to store recycled item views for reuse. 1665 * @see #setRecycledViewPool(RecycledViewPool) 1666 */ getRecycledViewPool()1667 public @NonNull RecycledViewPool getRecycledViewPool() { 1668 return mRecycler.getRecycledViewPool(); 1669 } 1670 1671 /** 1672 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 1673 * This can be useful if you have multiple RecyclerViews with adapters that use the same 1674 * view types, for example if you have several data sets with the same kinds of item views 1675 * displayed by a {@link androidx.viewpager.widget.ViewPager}. 1676 * 1677 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 1678 */ setRecycledViewPool(@ullable RecycledViewPool pool)1679 public void setRecycledViewPool(@Nullable RecycledViewPool pool) { 1680 mRecycler.setRecycledViewPool(pool); 1681 } 1682 1683 /** 1684 * Sets a new {@link ViewCacheExtension} to be used by the Recycler. 1685 * 1686 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. 1687 * @see ViewCacheExtension#getViewForPositionAndType(Recycler, int, int) 1688 */ setViewCacheExtension(@ullable ViewCacheExtension extension)1689 public void setViewCacheExtension(@Nullable ViewCacheExtension extension) { 1690 mRecycler.setViewCacheExtension(extension); 1691 } 1692 1693 /** 1694 * Set the number of offscreen views to retain before adding them to the potentially shared 1695 * {@link #getRecycledViewPool() recycled view pool}. 1696 * 1697 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 1698 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 1699 * to rebind them.</p> 1700 * 1701 * @param size Number of views to cache offscreen before returning them to the general 1702 * recycled view pool 1703 */ setItemViewCacheSize(int size)1704 public void setItemViewCacheSize(int size) { 1705 mRecycler.setViewCacheSize(size); 1706 } 1707 1708 /** 1709 * Return the current scrolling state of the RecyclerView. 1710 * 1711 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 1712 * {@link #SCROLL_STATE_SETTLING} 1713 */ getScrollState()1714 public int getScrollState() { 1715 return mScrollState; 1716 } 1717 setScrollState(int state)1718 void setScrollState(int state) { 1719 if (state == mScrollState) { 1720 return; 1721 } 1722 if (sVerboseLoggingEnabled) { 1723 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, 1724 new Exception()); 1725 } 1726 mScrollState = state; 1727 if (state != SCROLL_STATE_SETTLING) { 1728 stopScrollersInternal(); 1729 } 1730 dispatchOnScrollStateChanged(state); 1731 } 1732 1733 /** 1734 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1735 * affect both measurement and drawing of individual item views. 1736 * 1737 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1738 * be run/queried/drawn first for their effects on item views. Padding added to views 1739 * will be nested; a padding added by an earlier decoration will mean further 1740 * item decorations in the list will be asked to draw/pad within the previous decoration's 1741 * given area.</p> 1742 * 1743 * @param decor Decoration to add 1744 * @param index Position in the decoration chain to insert this decoration at. If this value 1745 * is negative the decoration will be added at the end. 1746 */ addItemDecoration(@onNull ItemDecoration decor, int index)1747 public void addItemDecoration(@NonNull ItemDecoration decor, int index) { 1748 if (mLayout != null) { 1749 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" 1750 + " layout"); 1751 } 1752 if (mItemDecorations.isEmpty()) { 1753 setWillNotDraw(false); 1754 } 1755 if (index < 0) { 1756 mItemDecorations.add(decor); 1757 } else { 1758 mItemDecorations.add(index, decor); 1759 } 1760 markItemDecorInsetsDirty(); 1761 requestLayout(); 1762 } 1763 1764 /** 1765 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 1766 * affect both measurement and drawing of individual item views. 1767 * 1768 * <p>Item decorations are ordered. Decorations placed earlier in the list will 1769 * be run/queried/drawn first for their effects on item views. Padding added to views 1770 * will be nested; a padding added by an earlier decoration will mean further 1771 * item decorations in the list will be asked to draw/pad within the previous decoration's 1772 * given area.</p> 1773 * 1774 * @param decor Decoration to add 1775 */ addItemDecoration(@onNull ItemDecoration decor)1776 public void addItemDecoration(@NonNull ItemDecoration decor) { 1777 addItemDecoration(decor, -1); 1778 } 1779 1780 /** 1781 * Returns an {@link ItemDecoration} previously added to this RecyclerView. 1782 * 1783 * @param index The index position of the desired ItemDecoration. 1784 * @return the ItemDecoration at index position 1785 * @throws IndexOutOfBoundsException on invalid index 1786 */ getItemDecorationAt(int index)1787 public @NonNull ItemDecoration getItemDecorationAt(int index) { 1788 final int size = getItemDecorationCount(); 1789 if (index < 0 || index >= size) { 1790 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1791 } 1792 1793 return mItemDecorations.get(index); 1794 } 1795 1796 /** 1797 * Returns the number of {@link ItemDecoration} currently added to this RecyclerView. 1798 * 1799 * @return number of ItemDecorations currently added added to this RecyclerView. 1800 */ getItemDecorationCount()1801 public int getItemDecorationCount() { 1802 return mItemDecorations.size(); 1803 } 1804 1805 /** 1806 * Removes the {@link ItemDecoration} associated with the supplied index position. 1807 * 1808 * @param index The index position of the ItemDecoration to be removed. 1809 */ removeItemDecorationAt(int index)1810 public void removeItemDecorationAt(int index) { 1811 final int size = getItemDecorationCount(); 1812 if (index < 0 || index >= size) { 1813 throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); 1814 } 1815 1816 removeItemDecoration(getItemDecorationAt(index)); 1817 } 1818 1819 /** 1820 * Remove an {@link ItemDecoration} from this RecyclerView. 1821 * 1822 * <p>The given decoration will no longer impact the measurement and drawing of 1823 * item views.</p> 1824 * 1825 * @param decor Decoration to remove 1826 * @see #addItemDecoration(ItemDecoration) 1827 */ removeItemDecoration(@onNull ItemDecoration decor)1828 public void removeItemDecoration(@NonNull ItemDecoration decor) { 1829 if (mLayout != null) { 1830 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" 1831 + " layout"); 1832 } 1833 mItemDecorations.remove(decor); 1834 if (mItemDecorations.isEmpty()) { 1835 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); 1836 } 1837 markItemDecorInsetsDirty(); 1838 requestLayout(); 1839 } 1840 1841 /** 1842 * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children. 1843 * <p> 1844 * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will 1845 * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be 1846 * true if childDrawingOrderCallback is not null, false otherwise. 1847 * <p> 1848 * Note that child drawing order may be overridden by View's elevation. 1849 * 1850 * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing 1851 * system. 1852 */ setChildDrawingOrderCallback( @ullable ChildDrawingOrderCallback childDrawingOrderCallback)1853 public void setChildDrawingOrderCallback( 1854 @Nullable ChildDrawingOrderCallback childDrawingOrderCallback) { 1855 if (childDrawingOrderCallback == mChildDrawingOrderCallback) { 1856 return; 1857 } 1858 mChildDrawingOrderCallback = childDrawingOrderCallback; 1859 setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null); 1860 } 1861 1862 /** 1863 * Set a listener that will be notified of any changes in scroll state or position. 1864 * 1865 * @param listener Listener to set or null to clear 1866 * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and 1867 * {@link #removeOnScrollListener(OnScrollListener)} 1868 */ 1869 @Deprecated setOnScrollListener(@ullable OnScrollListener listener)1870 public void setOnScrollListener(@Nullable OnScrollListener listener) { 1871 mScrollListener = listener; 1872 } 1873 1874 /** 1875 * Add a listener that will be notified of any changes in scroll state or position. 1876 * 1877 * <p>Components that add a listener should take care to remove it when finished. 1878 * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} 1879 * to remove all attached listeners.</p> 1880 * 1881 * @param listener listener to set 1882 */ addOnScrollListener(@onNull OnScrollListener listener)1883 public void addOnScrollListener(@NonNull OnScrollListener listener) { 1884 if (mScrollListeners == null) { 1885 mScrollListeners = new ArrayList<>(); 1886 } 1887 mScrollListeners.add(listener); 1888 } 1889 1890 /** 1891 * Remove a listener that was notified of any changes in scroll state or position. 1892 * 1893 * @param listener listener to set or null to clear 1894 */ removeOnScrollListener(@onNull OnScrollListener listener)1895 public void removeOnScrollListener(@NonNull OnScrollListener listener) { 1896 if (mScrollListeners != null) { 1897 mScrollListeners.remove(listener); 1898 } 1899 } 1900 1901 /** 1902 * Remove all secondary listener that were notified of any changes in scroll state or position. 1903 */ clearOnScrollListeners()1904 public void clearOnScrollListeners() { 1905 if (mScrollListeners != null) { 1906 mScrollListeners.clear(); 1907 } 1908 } 1909 1910 /** 1911 * Convenience method to scroll to a certain position. 1912 * 1913 * RecyclerView does not implement scrolling logic, rather forwards the call to 1914 * {@link RecyclerView.LayoutManager#scrollToPosition(int)} 1915 * 1916 * @param position Scroll to this adapter position 1917 * @see RecyclerView.LayoutManager#scrollToPosition(int) 1918 */ scrollToPosition(int position)1919 public void scrollToPosition(int position) { 1920 if (mLayoutSuppressed) { 1921 return; 1922 } 1923 stopScroll(); 1924 if (mLayout == null) { 1925 Log.e(TAG, "Cannot scroll to position a LayoutManager set. " 1926 + "Call setLayoutManager with a non-null argument."); 1927 return; 1928 } 1929 mLayout.scrollToPosition(position); 1930 awakenScrollBars(); 1931 } 1932 jumpToPositionForSmoothScroller(int position)1933 void jumpToPositionForSmoothScroller(int position) { 1934 if (mLayout == null) { 1935 return; 1936 } 1937 1938 // If we are jumping to a position, we are in fact scrolling the contents of the RV, so 1939 // we should be sure that we are in the settling state. 1940 setScrollState(SCROLL_STATE_SETTLING); 1941 mLayout.scrollToPosition(position); 1942 awakenScrollBars(); 1943 } 1944 1945 /** 1946 * Starts a smooth scroll to an adapter position. 1947 * <p> 1948 * To support smooth scrolling, you must override 1949 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a 1950 * {@link SmoothScroller}. 1951 * <p> 1952 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to 1953 * provide a custom smooth scroll logic, override 1954 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your 1955 * LayoutManager. 1956 * 1957 * @param position The adapter position to scroll to 1958 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) 1959 */ smoothScrollToPosition(int position)1960 public void smoothScrollToPosition(int position) { 1961 if (mLayoutSuppressed) { 1962 return; 1963 } 1964 if (mLayout == null) { 1965 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 1966 + "Call setLayoutManager with a non-null argument."); 1967 return; 1968 } 1969 mLayout.smoothScrollToPosition(this, mState, position); 1970 } 1971 1972 @Override scrollTo(int x, int y)1973 public void scrollTo(int x, int y) { 1974 Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " 1975 + "Use scrollToPosition instead"); 1976 } 1977 1978 @Override dispatchKeyEvent(@ullable KeyEvent event)1979 public boolean dispatchKeyEvent(@Nullable KeyEvent event) { 1980 // Let child to dispatch first, then handle ours if child didn't do it. 1981 if (super.dispatchKeyEvent(event)) { 1982 return true; 1983 } 1984 1985 LayoutManager layoutManager = getLayoutManager(); 1986 // If there is no layout manager, then there is nothing to handle key events for. 1987 if (layoutManager == null) { 1988 return false; 1989 } 1990 1991 if (layoutManager.canScrollVertically()) { 1992 final int keyCode = event.getKeyCode(); 1993 switch (keyCode) { 1994 case KeyEvent.KEYCODE_PAGE_DOWN: 1995 case KeyEvent.KEYCODE_PAGE_UP: 1996 int height = getMeasuredHeight(); 1997 if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) { 1998 smoothScrollBy(0, height, null, UNDEFINED_DURATION); 1999 } else { 2000 smoothScrollBy(0, -height, null, UNDEFINED_DURATION); 2001 } 2002 return true; 2003 2004 case KeyEvent.KEYCODE_MOVE_HOME: 2005 case KeyEvent.KEYCODE_MOVE_END: 2006 final boolean isReversed = layoutManager.isLayoutReversed(); 2007 2008 final int targetOffset; 2009 if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) { 2010 targetOffset = isReversed ? getAdapter().getItemCount() : 0; 2011 } else { 2012 targetOffset = isReversed ? 0 : getAdapter().getItemCount(); 2013 } 2014 2015 smoothScrollToPosition(targetOffset); 2016 return true; 2017 } 2018 } else if (layoutManager.canScrollHorizontally()) { 2019 final int keyCode = event.getKeyCode(); 2020 switch (keyCode) { 2021 case KeyEvent.KEYCODE_PAGE_DOWN: 2022 case KeyEvent.KEYCODE_PAGE_UP: 2023 int width = getMeasuredWidth(); 2024 if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) { 2025 smoothScrollBy(width, 0, null, UNDEFINED_DURATION); 2026 } else { 2027 smoothScrollBy(-width, 0, null, UNDEFINED_DURATION); 2028 } 2029 return true; 2030 2031 case KeyEvent.KEYCODE_MOVE_HOME: 2032 case KeyEvent.KEYCODE_MOVE_END: 2033 final boolean isReversed = layoutManager.isLayoutReversed(); 2034 2035 final int targetOffset; 2036 if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) { 2037 targetOffset = isReversed ? getAdapter().getItemCount() : 0; 2038 } else { 2039 targetOffset = isReversed ? 0 : getAdapter().getItemCount(); 2040 } 2041 2042 smoothScrollToPosition(targetOffset); 2043 return true; 2044 } 2045 } 2046 2047 return false; 2048 } 2049 2050 @Override scrollBy(int x, int y)2051 public void scrollBy(int x, int y) { 2052 if (mLayout == null) { 2053 Log.e(TAG, "Cannot scroll without a LayoutManager set. " 2054 + "Call setLayoutManager with a non-null argument."); 2055 return; 2056 } 2057 if (mLayoutSuppressed) { 2058 return; 2059 } 2060 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2061 final boolean canScrollVertical = mLayout.canScrollVertically(); 2062 if (canScrollHorizontal || canScrollVertical) { 2063 scrollByInternal( 2064 canScrollHorizontal ? x : 0, 2065 canScrollVertical ? y : 0, 2066 /* horizontalAxis= */ -1, 2067 /* verticalAxis= */ -1, 2068 /* ev= */ null, 2069 TYPE_TOUCH); 2070 } 2071 } 2072 2073 /** 2074 * Same as {@link RecyclerView#scrollBy(int, int)}, but also participates in nested scrolling. 2075 * @param x The amount of horizontal scroll requested 2076 * @param y The amount of vertical scroll requested 2077 * @see androidx.core.view.NestedScrollingChild 2078 */ nestedScrollBy(int x, int y)2079 public void nestedScrollBy(int x, int y) { 2080 nestedScrollByInternal(x, y, -1, -1, null, TYPE_NON_TOUCH); 2081 } 2082 2083 /** 2084 * Similar to {@link RecyclerView#scrollByInternal(int, int, int, int, MotionEvent, int)}, but 2085 * fully participates in nested scrolling "end to end", meaning that it will start nested 2086 * scrolling, participate in nested scrolling, and then end nested scrolling all within one 2087 * call. 2088 * 2089 * @param x The amount of horizontal scroll requested. 2090 * @param y The amount of vertical scroll requested. 2091 * @param horizontalAxis the {@link MotionEvent} axis that caused the {@code x} scroll, or -1 if 2092 * not known. 2093 * @param verticalAxis the {@link MotionEvent} axis that caused the {@code y} scroll, or -1 if 2094 * not known. 2095 * @param motionEvent The originating MotionEvent if any. 2096 * @param type The type of nested scrolling to engage in (TYPE_TOUCH or TYPE_NON_TOUCH). 2097 */ 2098 @SuppressWarnings("SameParameterValue") nestedScrollByInternal( int x, int y, int horizontalAxis, int verticalAxis, @Nullable MotionEvent motionEvent, int type)2099 private void nestedScrollByInternal( 2100 int x, 2101 int y, 2102 int horizontalAxis, 2103 int verticalAxis, 2104 @Nullable MotionEvent motionEvent, 2105 int type) { 2106 if (mLayout == null) { 2107 Log.e(TAG, "Cannot scroll without a LayoutManager set. " 2108 + "Call setLayoutManager with a non-null argument."); 2109 return; 2110 } 2111 if (mLayoutSuppressed) { 2112 return; 2113 } 2114 mReusableIntPair[0] = 0; 2115 mReusableIntPair[1] = 0; 2116 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2117 final boolean canScrollVertical = mLayout.canScrollVertically(); 2118 2119 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2120 if (canScrollHorizontal) { 2121 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2122 } 2123 if (canScrollVertical) { 2124 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2125 } 2126 2127 // If there is no MotionEvent, treat it as center-aligned edge effect: 2128 float verticalDisplacement = motionEvent == null ? getHeight() / 2f : motionEvent.getY(); 2129 float horizontalDisplacement = motionEvent == null ? getWidth() / 2f : motionEvent.getX(); 2130 x -= releaseHorizontalGlow(x, verticalDisplacement); 2131 y -= releaseVerticalGlow(y, horizontalDisplacement); 2132 startNestedScroll(nestedScrollAxis, type); 2133 if (dispatchNestedPreScroll( 2134 canScrollHorizontal ? x : 0, 2135 canScrollVertical ? y : 0, 2136 mReusableIntPair, mScrollOffset, type 2137 )) { 2138 x -= mReusableIntPair[0]; 2139 y -= mReusableIntPair[1]; 2140 } 2141 2142 scrollByInternal( 2143 canScrollHorizontal ? x : 0, 2144 canScrollVertical ? y : 0, 2145 horizontalAxis, 2146 verticalAxis, 2147 motionEvent, type); 2148 if (mGapWorker != null && (x != 0 || y != 0)) { 2149 mGapWorker.postFromTraversal(this, x, y); 2150 } 2151 stopNestedScroll(type); 2152 } 2153 2154 /** 2155 * Scrolls the RV by 'dx' and 'dy' via calls to 2156 * {@link LayoutManager#scrollHorizontallyBy(int, Recycler, State)} and 2157 * {@link LayoutManager#scrollVerticallyBy(int, Recycler, State)}. 2158 * 2159 * Also sets how much of the scroll was actually consumed in 'consumed' parameter (indexes 0 and 2160 * 1 for the x axis and y axis, respectively). 2161 * 2162 * This method should only be called in the context of an existing scroll operation such that 2163 * any other necessary operations (such as a call to {@link #consumePendingUpdateOperations()}) 2164 * is already handled. 2165 */ scrollStep(int dx, int dy, int @Nullable [] consumed)2166 void scrollStep(int dx, int dy, int @Nullable [] consumed) { 2167 startInterceptRequestLayout(); 2168 onEnterLayoutOrScroll(); 2169 2170 Trace.beginSection(TRACE_SCROLL_TAG); 2171 fillRemainingScrollValues(mState); 2172 2173 int consumedX = 0; 2174 int consumedY = 0; 2175 if (dx != 0) { 2176 consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); 2177 } 2178 if (dy != 0) { 2179 consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); 2180 } 2181 2182 Trace.endSection(); 2183 repositionShadowingViews(); 2184 2185 onExitLayoutOrScroll(); 2186 stopInterceptRequestLayout(false); 2187 2188 if (consumed != null) { 2189 consumed[0] = consumedX; 2190 consumed[1] = consumedY; 2191 } 2192 } 2193 2194 /** 2195 * Helper method reflect data changes to the state. 2196 * <p> 2197 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change 2198 * but data actually changed. 2199 * <p> 2200 * This method consumes all deferred changes to avoid that case. 2201 */ consumePendingUpdateOperations()2202 void consumePendingUpdateOperations() { 2203 if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { 2204 Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 2205 dispatchLayout(); 2206 Trace.endSection(); 2207 return; 2208 } 2209 if (!mAdapterHelper.hasPendingUpdates()) { 2210 return; 2211 } 2212 2213 // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any 2214 // of the visible items is affected and if not, just ignore the change. 2215 if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper 2216 .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE 2217 | AdapterHelper.UpdateOp.MOVE)) { 2218 Trace.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); 2219 startInterceptRequestLayout(); 2220 onEnterLayoutOrScroll(); 2221 mAdapterHelper.preProcess(); 2222 if (!mLayoutWasDefered) { 2223 if (hasUpdatedView()) { 2224 dispatchLayout(); 2225 } else { 2226 // no need to layout, clean state 2227 mAdapterHelper.consumePostponedUpdates(); 2228 } 2229 } 2230 stopInterceptRequestLayout(true); 2231 onExitLayoutOrScroll(); 2232 Trace.endSection(); 2233 } else if (mAdapterHelper.hasPendingUpdates()) { 2234 Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); 2235 dispatchLayout(); 2236 Trace.endSection(); 2237 } 2238 } 2239 2240 /** 2241 * @return True if an existing view holder needs to be updated 2242 */ hasUpdatedView()2243 private boolean hasUpdatedView() { 2244 final int childCount = mChildHelper.getChildCount(); 2245 for (int i = 0; i < childCount; i++) { 2246 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 2247 if (holder == null || holder.shouldIgnore()) { 2248 continue; 2249 } 2250 if (holder.isUpdated()) { 2251 return true; 2252 } 2253 } 2254 return false; 2255 } 2256 2257 /** 2258 * Does not perform bounds checking. Used by internal methods that have already validated input. 2259 * <p> 2260 * It also reports any unused scroll request to the related EdgeEffect. 2261 * 2262 * @param x The amount of horizontal scroll request 2263 * @param y The amount of vertical scroll request 2264 * @param horizontalAxis the {@link MotionEvent} axis that caused the {@code x} scroll, or -1 if 2265 * not known. 2266 * @param verticalAxis the {@link MotionEvent} axis that caused the {@code y} scroll, or -1 if 2267 * not known. 2268 * @param ev The originating MotionEvent, or null if unknown. 2269 * @param type NestedScrollType, TOUCH or NON_TOUCH. 2270 * @return Whether any scroll was consumed in either direction. 2271 */ scrollByInternal( int x, int y, int horizontalAxis, int verticalAxis, @Nullable MotionEvent ev, int type)2272 boolean scrollByInternal( 2273 int x, 2274 int y, 2275 int horizontalAxis, 2276 int verticalAxis, 2277 @Nullable MotionEvent ev, 2278 int type) { 2279 int unconsumedX = 0; 2280 int unconsumedY = 0; 2281 int consumedX = 0; 2282 int consumedY = 0; 2283 2284 consumePendingUpdateOperations(); 2285 if (mAdapter != null) { 2286 mReusableIntPair[0] = 0; 2287 mReusableIntPair[1] = 0; 2288 scrollStep(x, y, mReusableIntPair); 2289 consumedX = mReusableIntPair[0]; 2290 consumedY = mReusableIntPair[1]; 2291 unconsumedX = x - consumedX; 2292 unconsumedY = y - consumedY; 2293 } 2294 if (!mItemDecorations.isEmpty()) { 2295 invalidate(); 2296 } 2297 2298 mReusableIntPair[0] = 0; 2299 mReusableIntPair[1] = 0; 2300 dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, 2301 type, mReusableIntPair); 2302 unconsumedX -= mReusableIntPair[0]; 2303 unconsumedY -= mReusableIntPair[1]; 2304 boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0; 2305 2306 // Update the last touch co-ords, taking any scroll offset into account 2307 mLastTouchX -= mScrollOffset[0]; 2308 mLastTouchY -= mScrollOffset[1]; 2309 mNestedOffsets[0] += mScrollOffset[0]; 2310 mNestedOffsets[1] += mScrollOffset[1]; 2311 2312 if (ev != null) { 2313 if (consumedX != 0) { 2314 getScrollFeedbackProvider().onScrollProgress( 2315 ev.getDeviceId(), ev.getSource(), horizontalAxis, consumedX); 2316 } 2317 if (consumedY != 0) { 2318 getScrollFeedbackProvider().onScrollProgress( 2319 ev.getDeviceId(), ev.getSource(), verticalAxis, consumedY); 2320 } 2321 } 2322 2323 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 2324 if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) { 2325 pullGlows(ev, ev.getX(), horizontalAxis, unconsumedX, ev.getY(), verticalAxis, 2326 unconsumedY); 2327 // For rotary encoders, we release stretch EdgeEffects after they are pulled, to 2328 // avoid the effects being stuck pulled. 2329 if (Build.VERSION.SDK_INT >= 31 2330 && MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_ROTARY_ENCODER)) { 2331 releaseGlows(); 2332 } 2333 } 2334 considerReleasingGlowsOnScroll(x, y); 2335 } 2336 if (consumedX != 0 || consumedY != 0) { 2337 dispatchOnScrolled(consumedX, consumedY); 2338 } 2339 if (!awakenScrollBars()) { 2340 invalidate(); 2341 } 2342 return consumedNestedScroll || consumedX != 0 || consumedY != 0; 2343 } 2344 2345 /** 2346 * If either of the horizontal edge glows are currently active, this consumes part or all of 2347 * deltaX on the edge glow. 2348 * 2349 * @param deltaX The pointer motion, in pixels, in the horizontal direction, positive 2350 * for moving down and negative for moving up. 2351 * @param y The vertical position of the pointer. 2352 * @return The amount of <code>deltaX</code> that has been consumed by the 2353 * edge glow. 2354 */ releaseHorizontalGlow(int deltaX, float y)2355 private int releaseHorizontalGlow(int deltaX, float y) { 2356 // First allow releasing existing overscroll effect: 2357 float consumed = 0; 2358 float displacement = y / getHeight(); 2359 float pullDistance = (float) deltaX / getWidth(); 2360 if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0) { 2361 if (canScrollHorizontally(-1)) { 2362 mLeftGlow.onRelease(); 2363 } else { 2364 consumed = -EdgeEffectCompat.onPullDistance(mLeftGlow, -pullDistance, 2365 1 - displacement); 2366 if (EdgeEffectCompat.getDistance(mLeftGlow) == 0) { 2367 mLeftGlow.onRelease(); 2368 } 2369 } 2370 invalidate(); 2371 } else if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0) { 2372 if (canScrollHorizontally(1)) { 2373 mRightGlow.onRelease(); 2374 } else { 2375 consumed = EdgeEffectCompat.onPullDistance(mRightGlow, pullDistance, displacement); 2376 if (EdgeEffectCompat.getDistance(mRightGlow) == 0) { 2377 mRightGlow.onRelease(); 2378 } 2379 } 2380 invalidate(); 2381 } 2382 return Math.round(consumed * getWidth()); 2383 } 2384 2385 /** 2386 * If either of the vertical edge glows are currently active, this consumes part or all of 2387 * deltaY on the edge glow. 2388 * 2389 * @param deltaY The pointer motion, in pixels, in the vertical direction, positive 2390 * for moving down and negative for moving up. 2391 * @param x The vertical position of the pointer. 2392 * @return The amount of <code>deltaY</code> that has been consumed by the 2393 * edge glow. 2394 */ releaseVerticalGlow(int deltaY, float x)2395 private int releaseVerticalGlow(int deltaY, float x) { 2396 // First allow releasing existing overscroll effect: 2397 float consumed = 0; 2398 float displacement = x / getWidth(); 2399 float pullDistance = (float) deltaY / getHeight(); 2400 if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0) { 2401 if (canScrollVertically(-1)) { 2402 mTopGlow.onRelease(); 2403 } else { 2404 consumed = -EdgeEffectCompat.onPullDistance(mTopGlow, -pullDistance, displacement); 2405 if (EdgeEffectCompat.getDistance(mTopGlow) == 0) { 2406 mTopGlow.onRelease(); 2407 } 2408 } 2409 invalidate(); 2410 } else if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0) { 2411 if (canScrollVertically(1)) { 2412 mBottomGlow.onRelease(); 2413 } else { 2414 consumed = EdgeEffectCompat.onPullDistance(mBottomGlow, pullDistance, 2415 1 - displacement); 2416 if (EdgeEffectCompat.getDistance(mBottomGlow) == 0) { 2417 mBottomGlow.onRelease(); 2418 } 2419 } 2420 invalidate(); 2421 } 2422 return Math.round(consumed * getHeight()); 2423 } 2424 2425 /** 2426 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal 2427 * range. This value is used to compute the position of the thumb within the scrollbar's track. 2428 * </p> 2429 * 2430 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2431 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> 2432 * 2433 * <p>Default implementation returns 0.</p> 2434 * 2435 * <p>If you want to support scroll bars, override 2436 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your 2437 * LayoutManager. </p> 2438 * 2439 * @return The horizontal offset of the scrollbar's thumb 2440 * @see RecyclerView.LayoutManager#computeHorizontalScrollOffset 2441 * (RecyclerView.State) 2442 */ 2443 @Override computeHorizontalScrollOffset()2444 public int computeHorizontalScrollOffset() { 2445 if (mLayout == null) { 2446 return 0; 2447 } 2448 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; 2449 } 2450 2451 /** 2452 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the 2453 * horizontal range. This value is used to compute the length of the thumb within the 2454 * scrollbar's track.</p> 2455 * 2456 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2457 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> 2458 * 2459 * <p>Default implementation returns 0.</p> 2460 * 2461 * <p>If you want to support scroll bars, override 2462 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your 2463 * LayoutManager.</p> 2464 * 2465 * @return The horizontal extent of the scrollbar's thumb 2466 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) 2467 */ 2468 @Override computeHorizontalScrollExtent()2469 public int computeHorizontalScrollExtent() { 2470 if (mLayout == null) { 2471 return 0; 2472 } 2473 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; 2474 } 2475 2476 /** 2477 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> 2478 * 2479 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2480 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> 2481 * 2482 * <p>Default implementation returns 0.</p> 2483 * 2484 * <p>If you want to support scroll bars, override 2485 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your 2486 * LayoutManager.</p> 2487 * 2488 * @return The total horizontal range represented by the horizontal scrollbar 2489 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) 2490 */ 2491 @Override computeHorizontalScrollRange()2492 public int computeHorizontalScrollRange() { 2493 if (mLayout == null) { 2494 return 0; 2495 } 2496 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; 2497 } 2498 2499 /** 2500 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. 2501 * This value is used to compute the position of the thumb within the scrollbar's track. </p> 2502 * 2503 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2504 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> 2505 * 2506 * <p>Default implementation returns 0.</p> 2507 * 2508 * <p>If you want to support scroll bars, override 2509 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your 2510 * LayoutManager.</p> 2511 * 2512 * @return The vertical offset of the scrollbar's thumb 2513 * @see RecyclerView.LayoutManager#computeVerticalScrollOffset 2514 * (RecyclerView.State) 2515 */ 2516 @Override computeVerticalScrollOffset()2517 public int computeVerticalScrollOffset() { 2518 if (mLayout == null) { 2519 return 0; 2520 } 2521 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; 2522 } 2523 2524 /** 2525 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. 2526 * This value is used to compute the length of the thumb within the scrollbar's track.</p> 2527 * 2528 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2529 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> 2530 * 2531 * <p>Default implementation returns 0.</p> 2532 * 2533 * <p>If you want to support scroll bars, override 2534 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your 2535 * LayoutManager.</p> 2536 * 2537 * @return The vertical extent of the scrollbar's thumb 2538 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) 2539 */ 2540 @Override computeVerticalScrollExtent()2541 public int computeVerticalScrollExtent() { 2542 if (mLayout == null) { 2543 return 0; 2544 } 2545 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; 2546 } 2547 2548 /** 2549 * <p>Compute the vertical range that the vertical scrollbar represents.</p> 2550 * 2551 * <p>The range is expressed in arbitrary units that must be the same as the units used by 2552 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> 2553 * 2554 * <p>Default implementation returns 0.</p> 2555 * 2556 * <p>If you want to support scroll bars, override 2557 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your 2558 * LayoutManager.</p> 2559 * 2560 * @return The total vertical range represented by the vertical scrollbar 2561 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) 2562 */ 2563 @Override computeVerticalScrollRange()2564 public int computeVerticalScrollRange() { 2565 if (mLayout == null) { 2566 return 0; 2567 } 2568 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; 2569 } 2570 2571 /** 2572 * This method should be called before any code that may trigger a child view to cause a call to 2573 * {@link RecyclerView#requestLayout()}. Doing so enables {@link RecyclerView} to avoid 2574 * reacting to additional redundant calls to {@link #requestLayout()}. 2575 * <p> 2576 * A call to this method must always be accompanied by a call to 2577 * {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a 2578 * child View to cause a call to {@link RecyclerView#requestLayout()}. 2579 * 2580 * @see #stopInterceptRequestLayout(boolean) 2581 */ startInterceptRequestLayout()2582 void startInterceptRequestLayout() { 2583 mInterceptRequestLayoutDepth++; 2584 if (mInterceptRequestLayoutDepth == 1 && !mLayoutSuppressed) { 2585 mLayoutWasDefered = false; 2586 } 2587 } 2588 2589 /** 2590 * This method should be called after any code that may trigger a child view to cause a call to 2591 * {@link RecyclerView#requestLayout()}. 2592 * <p> 2593 * A call to this method must always be accompanied by a call to 2594 * {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child 2595 * View to cause a call to {@link RecyclerView#requestLayout()}. 2596 * 2597 * @see #startInterceptRequestLayout() 2598 */ stopInterceptRequestLayout(boolean performLayoutChildren)2599 void stopInterceptRequestLayout(boolean performLayoutChildren) { 2600 if (mInterceptRequestLayoutDepth < 1) { 2601 //noinspection PointlessBooleanExpression 2602 if (sDebugAssertionsEnabled) { 2603 throw new IllegalStateException("stopInterceptRequestLayout was called more " 2604 + "times than startInterceptRequestLayout." 2605 + exceptionLabel()); 2606 } 2607 mInterceptRequestLayoutDepth = 1; 2608 } 2609 if (!performLayoutChildren && !mLayoutSuppressed) { 2610 // Reset the layout request eaten counter. 2611 // This is necessary since eatRequest calls can be nested in which case the other 2612 // call will override the inner one. 2613 // for instance: 2614 // eat layout for process adapter updates 2615 // eat layout for dispatchLayout 2616 // a bunch of req layout calls arrive 2617 2618 mLayoutWasDefered = false; 2619 } 2620 if (mInterceptRequestLayoutDepth == 1) { 2621 // when layout is frozen we should delay dispatchLayout() 2622 if (performLayoutChildren && mLayoutWasDefered && !mLayoutSuppressed 2623 && mLayout != null && mAdapter != null) { 2624 dispatchLayout(); 2625 } 2626 if (!mLayoutSuppressed) { 2627 mLayoutWasDefered = false; 2628 } 2629 } 2630 mInterceptRequestLayoutDepth--; 2631 } 2632 2633 /** 2634 * Tells this RecyclerView to suppress all layout and scroll calls until layout 2635 * suppression is disabled with a later call to suppressLayout(false). 2636 * When layout suppression is disabled, a requestLayout() call is sent 2637 * if requestLayout() was attempted while layout was being suppressed. 2638 * <p> 2639 * In addition to the layout suppression {@link #smoothScrollBy(int, int)}, 2640 * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and 2641 * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are 2642 * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be 2643 * called. 2644 * 2645 * <p> 2646 * <code>suppressLayout(true)</code> does not prevent app from directly calling {@link 2647 * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( 2648 *RecyclerView, State, int)}. 2649 * <p> 2650 * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically 2651 * stop suppressing. 2652 * <p> 2653 * Note: Running ItemAnimator is not stopped automatically, it's caller's 2654 * responsibility to call ItemAnimator.end(). 2655 * 2656 * @param suppress true to suppress layout and scroll, false to re-enable. 2657 */ 2658 @Override suppressLayout(boolean suppress)2659 public final void suppressLayout(boolean suppress) { 2660 if (suppress != mLayoutSuppressed) { 2661 assertNotInLayoutOrScroll("Do not suppressLayout in layout or scroll"); 2662 if (!suppress) { 2663 mLayoutSuppressed = false; 2664 if (mLayoutWasDefered && mLayout != null && mAdapter != null) { 2665 requestLayout(); 2666 } 2667 mLayoutWasDefered = false; 2668 } else { 2669 final long now = SystemClock.uptimeMillis(); 2670 MotionEvent cancelEvent = MotionEvent.obtain(now, now, 2671 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 2672 onTouchEvent(cancelEvent); 2673 mLayoutSuppressed = true; 2674 mIgnoreMotionEventTillDown = true; 2675 stopScroll(); 2676 } 2677 } 2678 } 2679 2680 /** 2681 * Returns whether layout and scroll calls on this container are currently being 2682 * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. 2683 * 2684 * @return true if layout and scroll are currently suppressed, false otherwise. 2685 */ 2686 @Override isLayoutSuppressed()2687 public final boolean isLayoutSuppressed() { 2688 return mLayoutSuppressed; 2689 } 2690 2691 /** 2692 * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called, 2693 * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called; 2694 * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)}, 2695 * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and 2696 * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are 2697 * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be 2698 * called. 2699 * 2700 * <p> 2701 * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link 2702 * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( 2703 *RecyclerView, State, int)}. 2704 * <p> 2705 * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically 2706 * stop frozen. 2707 * <p> 2708 * Note: Running ItemAnimator is not stopped automatically, it's caller's 2709 * responsibility to call ItemAnimator.end(). 2710 * 2711 * @param frozen true to freeze layout and scroll, false to re-enable. 2712 * @deprecated Use {@link #suppressLayout(boolean)}. 2713 */ 2714 @Deprecated setLayoutFrozen(boolean frozen)2715 public void setLayoutFrozen(boolean frozen) { 2716 suppressLayout(frozen); 2717 } 2718 2719 /** 2720 * @return true if layout and scroll are frozen 2721 * @deprecated Use {@link #isLayoutSuppressed()}. 2722 */ 2723 @Deprecated isLayoutFrozen()2724 public boolean isLayoutFrozen() { 2725 return isLayoutSuppressed(); 2726 } 2727 2728 /** 2729 * @deprecated Use {@link #setItemAnimator(ItemAnimator)} ()}. 2730 */ 2731 @Deprecated 2732 @Override setLayoutTransition(LayoutTransition transition)2733 public void setLayoutTransition(LayoutTransition transition) { 2734 if (transition == null) { 2735 super.setLayoutTransition(null); 2736 } else { 2737 throw new IllegalArgumentException("Providing a LayoutTransition into RecyclerView is " 2738 + "not supported. Please use setItemAnimator() instead for animating changes " 2739 + "to the items in this RecyclerView"); 2740 } 2741 } 2742 2743 /** 2744 * Animate a scroll by the given amount of pixels along either axis. 2745 * 2746 * @param dx Pixels to scroll horizontally 2747 * @param dy Pixels to scroll vertically 2748 */ smoothScrollBy(@x int dx, @Px int dy)2749 public void smoothScrollBy(@Px int dx, @Px int dy) { 2750 smoothScrollBy(dx, dy, null); 2751 } 2752 2753 /** 2754 * Animate a scroll by the given amount of pixels along either axis. 2755 * 2756 * @param dx Pixels to scroll horizontally 2757 * @param dy Pixels to scroll vertically 2758 * @param interpolator {@link Interpolator} to be used for scrolling. If it is 2759 * {@code null}, RecyclerView will use an internal default interpolator. 2760 */ smoothScrollBy(@x int dx, @Px int dy, @Nullable Interpolator interpolator)2761 public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) { 2762 smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION); 2763 } 2764 2765 /** 2766 * Smooth scrolls the RecyclerView by a given distance. 2767 * 2768 * @param dx x distance in pixels. 2769 * @param dy y distance in pixels. 2770 * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null}, 2771 * RecyclerView will use an internal default interpolator. 2772 * @param duration Duration of the animation in milliseconds. Set to 2773 * {@link #UNDEFINED_DURATION} 2774 * to have the duration be automatically calculated based on an internally 2775 * defined standard initial velocity. A duration less than 1 (that does not 2776 * equal UNDEFINED_DURATION), will result in a call to 2777 * {@link #scrollBy(int, int)}. 2778 */ smoothScrollBy(@x int dx, @Px int dy, @Nullable Interpolator interpolator, int duration)2779 public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator, 2780 int duration) { 2781 smoothScrollBy(dx, dy, interpolator, duration, false); 2782 } 2783 2784 /** 2785 * Internal smooth scroll by implementation that currently has some tricky logic related to it's 2786 * parameters. 2787 * <ul> 2788 * <li>For scrolling to occur, on either dimension, dx or dy must not be equal to 0 and the 2789 * {@link LayoutManager} must support scrolling in a direction for which the value is not 0. 2790 * <li>For smooth scrolling to occur, scrolling must occur and the duration must be equal to 2791 * {@link #UNDEFINED_DURATION} or greater than 0. 2792 * <li>For scrolling to occur with nested scrolling, smooth scrolling must occur and 2793 * {@code withNestedScrolling} must be {@code true}. This could be updated, but it would 2794 * require that {@link #scrollBy(int, int)} be implemented such that it too can handle nested 2795 * scrolling. 2796 * </ul> 2797 * 2798 * @param dx x distance in pixels. 2799 * @param dy y distance in pixels. 2800 * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code 2801 * null}, 2802 * RecyclerView will use an internal default interpolator. 2803 * @param duration Duration of the animation in milliseconds. Set to 2804 * {@link #UNDEFINED_DURATION} 2805 * to have the duration be automatically calculated based on an 2806 * internally 2807 * defined standard initial velocity. A duration less than 1 (that 2808 * does not 2809 * equal UNDEFINED_DURATION), will result in a call to 2810 * {@link #scrollBy(int, int)}. 2811 * @param withNestedScrolling True to perform the smooth scroll with nested scrolling. If 2812 * {@code duration} is less than 0 and not equal to 2813 * {@link #UNDEFINED_DURATION}, smooth scrolling will not occur and 2814 * thus no nested scrolling will occur. 2815 */ 2816 // Should be considered private. Not private to avoid synthetic accessor. smoothScrollBy(@x int dx, @Px int dy, @Nullable Interpolator interpolator, int duration, boolean withNestedScrolling)2817 void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator, 2818 int duration, boolean withNestedScrolling) { 2819 if (mLayout == null) { 2820 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " 2821 + "Call setLayoutManager with a non-null argument."); 2822 return; 2823 } 2824 if (mLayoutSuppressed) { 2825 return; 2826 } 2827 if (!mLayout.canScrollHorizontally()) { 2828 dx = 0; 2829 } 2830 if (!mLayout.canScrollVertically()) { 2831 dy = 0; 2832 } 2833 if (dx != 0 || dy != 0) { 2834 boolean durationSuggestsAnimation = duration == UNDEFINED_DURATION || duration > 0; 2835 if (durationSuggestsAnimation) { 2836 if (withNestedScrolling) { 2837 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2838 if (dx != 0) { 2839 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2840 } 2841 if (dy != 0) { 2842 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2843 } 2844 startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH); 2845 } 2846 mViewFlinger.smoothScrollBy(dx, dy, duration, interpolator); 2847 } else { 2848 scrollBy(dx, dy); 2849 } 2850 } 2851 } 2852 2853 /** 2854 * Begin a standard fling with an initial velocity along each axis in pixels per second. 2855 * If the velocity given is below the system-defined minimum this method will return false 2856 * and no fling will occur. 2857 * 2858 * @param velocityX Initial horizontal velocity in pixels per second 2859 * @param velocityY Initial vertical velocity in pixels per second 2860 * @return true if the fling was started, false if the velocity was too low to fling or 2861 * LayoutManager does not support scrolling in the axis fling is issued. 2862 * @see LayoutManager#canScrollVertically() 2863 * @see LayoutManager#canScrollHorizontally() 2864 */ fling(int velocityX, int velocityY)2865 public boolean fling(int velocityX, int velocityY) { 2866 return fling(velocityX, velocityY, mMinFlingVelocity, mMaxFlingVelocity); 2867 } 2868 2869 /** Flings without checking fling velocity thresholds. */ flingNoThresholdCheck(int velocityX, int velocityY)2870 boolean flingNoThresholdCheck(int velocityX, int velocityY) { 2871 return fling(velocityX, velocityY, 0, Integer.MAX_VALUE); 2872 } 2873 fling(int velocityX, int velocityY, int minFlingVelocity, int maxFlingVelocity)2874 private boolean fling(int velocityX, int velocityY, int minFlingVelocity, 2875 int maxFlingVelocity) { 2876 if (mLayout == null) { 2877 Log.e(TAG, "Cannot fling without a LayoutManager set. " 2878 + "Call setLayoutManager with a non-null argument."); 2879 return false; 2880 } 2881 if (mLayoutSuppressed) { 2882 return false; 2883 } 2884 2885 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2886 final boolean canScrollVertical = mLayout.canScrollVertically(); 2887 2888 if (!canScrollHorizontal || Math.abs(velocityX) < minFlingVelocity) { 2889 velocityX = 0; 2890 } 2891 if (!canScrollVertical || Math.abs(velocityY) < minFlingVelocity) { 2892 velocityY = 0; 2893 } 2894 if (velocityX == 0 && velocityY == 0) { 2895 // If we don't have any velocity, return false 2896 return false; 2897 } 2898 2899 // Flinging while the edge effect is active should affect the edge effect, 2900 // not scrolling. 2901 int flingX = 0; 2902 int flingY = 0; 2903 if (velocityX != 0) { 2904 if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0) { 2905 if (shouldAbsorb(mLeftGlow, -velocityX, getWidth())) { 2906 mLeftGlow.onAbsorb(-velocityX); 2907 } else { 2908 flingX = velocityX; 2909 } 2910 velocityX = 0; 2911 } else if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0) { 2912 if (shouldAbsorb(mRightGlow, velocityX, getWidth())) { 2913 mRightGlow.onAbsorb(velocityX); 2914 } else { 2915 flingX = velocityX; 2916 } 2917 velocityX = 0; 2918 } 2919 } 2920 if (velocityY != 0) { 2921 if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0) { 2922 if (shouldAbsorb(mTopGlow, -velocityY, getHeight())) { 2923 mTopGlow.onAbsorb(-velocityY); 2924 } else { 2925 flingY = velocityY; 2926 } 2927 velocityY = 0; 2928 } else if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0) { 2929 if (shouldAbsorb(mBottomGlow, velocityY, getHeight())) { 2930 mBottomGlow.onAbsorb(velocityY); 2931 } else { 2932 flingY = velocityY; 2933 } 2934 velocityY = 0; 2935 } 2936 } 2937 if (flingX != 0 || flingY != 0) { 2938 flingX = Math.max(-maxFlingVelocity, Math.min(flingX, maxFlingVelocity)); 2939 flingY = Math.max(-maxFlingVelocity, Math.min(flingY, maxFlingVelocity)); 2940 startNestedScrollForType(TYPE_NON_TOUCH); 2941 mViewFlinger.fling(flingX, flingY); 2942 } 2943 if (velocityX == 0 && velocityY == 0) { 2944 return flingX != 0 || flingY != 0; 2945 } 2946 2947 if (!dispatchNestedPreFling(velocityX, velocityY)) { 2948 final boolean canScroll = canScrollHorizontal || canScrollVertical; 2949 dispatchNestedFling(velocityX, velocityY, canScroll); 2950 2951 if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { 2952 return true; 2953 } 2954 2955 if (canScroll) { 2956 startNestedScrollForType(TYPE_NON_TOUCH); 2957 velocityX = Math.max(-maxFlingVelocity, Math.min(velocityX, maxFlingVelocity)); 2958 velocityY = Math.max(-maxFlingVelocity, Math.min(velocityY, maxFlingVelocity)); 2959 mViewFlinger.fling(velocityX, velocityY); 2960 return true; 2961 } 2962 } 2963 return false; 2964 } 2965 startNestedScrollForType(int type)2966 private void startNestedScrollForType(int type) { 2967 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 2968 final boolean canScrollVertical = mLayout.canScrollVertically(); 2969 int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 2970 if (canScrollHorizontal) { 2971 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 2972 } 2973 if (canScrollVertical) { 2974 nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 2975 } 2976 startNestedScroll(nestedScrollAxis, type); 2977 } 2978 2979 /** 2980 * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should 2981 * animate with a fling. It will animate with a fling if the velocity will remove the 2982 * EdgeEffect through its normal operation. 2983 * 2984 * @param edgeEffect The EdgeEffect that might absorb the velocity. 2985 * @param velocity The velocity of the fling motion 2986 * @param size The width or height of the RecyclerView, depending on the edge that the 2987 * EdgeEffect is on. 2988 * @return true if the velocity should be absorbed or false if it should be flung. 2989 */ shouldAbsorb(@onNull EdgeEffect edgeEffect, int velocity, int size)2990 private boolean shouldAbsorb(@NonNull EdgeEffect edgeEffect, int velocity, int size) { 2991 if (velocity > 0) { 2992 return true; 2993 } 2994 float distance = EdgeEffectCompat.getDistance(edgeEffect) * size; 2995 2996 // This is flinging without the spring, so let's see if it will fling past the overscroll 2997 float flingDistance = getSplineFlingDistance(-velocity); 2998 2999 return flingDistance < distance; 3000 } 3001 3002 /** 3003 * If mLeftGlow or mRightGlow is currently active and the motion will remove some of the 3004 * stretch, this will consume any of unconsumedX that the glow can. If the motion would 3005 * increase the stretch, or the EdgeEffect isn't a stretch, then nothing will be consumed. 3006 * 3007 * @param unconsumedX The horizontal delta that might be consumed by the horizontal EdgeEffects 3008 * @return The remaining unconsumed delta after the edge effects have consumed. 3009 */ consumeFlingInHorizontalStretch(int unconsumedX)3010 int consumeFlingInHorizontalStretch(int unconsumedX) { 3011 return consumeFlingInStretch(unconsumedX, mLeftGlow, mRightGlow, getWidth()); 3012 } 3013 3014 /** 3015 * If mTopGlow or mBottomGlow is currently active and the motion will remove some of the 3016 * stretch, this will consume any of unconsumedY that the glow can. If the motion would 3017 * increase the stretch, or the EdgeEffect isn't a stretch, then nothing will be consumed. 3018 * 3019 * @param unconsumedY The vertical delta that might be consumed by the vertical EdgeEffects 3020 * @return The remaining unconsumed delta after the edge effects have consumed. 3021 */ consumeFlingInVerticalStretch(int unconsumedY)3022 int consumeFlingInVerticalStretch(int unconsumedY) { 3023 return consumeFlingInStretch(unconsumedY, mTopGlow, mBottomGlow, getHeight()); 3024 } 3025 3026 /** 3027 * Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for 3028 * consuming deltas from EdgeEffects 3029 * @param unconsumed The unconsumed delta that the EdgeEffets may consume 3030 * @param startGlow The start (top or left) EdgeEffect 3031 * @param endGlow The end (bottom or right) EdgeEffect 3032 * @param size The width or height of the container, depending on whether this is for 3033 * horizontal or vertical EdgeEffects 3034 * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. 3035 */ consumeFlingInStretch( int unconsumed, EdgeEffect startGlow, EdgeEffect endGlow, int size )3036 private int consumeFlingInStretch( 3037 int unconsumed, 3038 EdgeEffect startGlow, 3039 EdgeEffect endGlow, 3040 int size 3041 ) { 3042 if (unconsumed > 0 && startGlow != null && EdgeEffectCompat.getDistance(startGlow) != 0f) { 3043 float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; 3044 int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR 3045 * EdgeEffectCompat.onPullDistance(startGlow, deltaDistance, 0.5f)); 3046 if (consumed != unconsumed) { 3047 startGlow.finish(); 3048 } 3049 return unconsumed - consumed; 3050 } 3051 if (unconsumed < 0 && endGlow != null && EdgeEffectCompat.getDistance(endGlow) != 0f) { 3052 float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size; 3053 int consumed = Math.round(size / FLING_DESTRETCH_FACTOR 3054 * EdgeEffectCompat.onPullDistance(endGlow, deltaDistance, 0.5f)); 3055 if (consumed != unconsumed) { 3056 endGlow.finish(); 3057 } 3058 return unconsumed - consumed; 3059 } 3060 return unconsumed; 3061 } 3062 3063 /** 3064 * Stop any current scroll in progress, such as one started by 3065 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 3066 */ stopScroll()3067 public void stopScroll() { 3068 setScrollState(SCROLL_STATE_IDLE); 3069 stopScrollersInternal(); 3070 } 3071 3072 /** 3073 * Similar to {@link #stopScroll()} but does not set the state. 3074 */ stopScrollersInternal()3075 private void stopScrollersInternal() { 3076 mViewFlinger.stop(); 3077 if (mLayout != null) { 3078 mLayout.stopSmoothScroller(); 3079 } 3080 } 3081 3082 /** 3083 * Returns the minimum velocity to start a fling. 3084 * 3085 * @return The minimum velocity to start a fling 3086 */ getMinFlingVelocity()3087 public int getMinFlingVelocity() { 3088 return mMinFlingVelocity; 3089 } 3090 3091 3092 /** 3093 * Returns the maximum fling velocity used by this RecyclerView. 3094 * 3095 * @return The maximum fling velocity used by this RecyclerView. 3096 */ getMaxFlingVelocity()3097 public int getMaxFlingVelocity() { 3098 return mMaxFlingVelocity; 3099 } 3100 3101 /** 3102 * Apply a pull to relevant overscroll glow effects 3103 */ pullGlows( MotionEvent ev, float x, int horizontalAxis, float overscrollX, float y, int verticalAxis, float overscrollY)3104 private void pullGlows( 3105 MotionEvent ev, 3106 float x, 3107 int horizontalAxis, 3108 float overscrollX, 3109 float y, 3110 int verticalAxis, 3111 float overscrollY) { 3112 boolean invalidate = false; 3113 if (overscrollX < 0) { 3114 ensureLeftGlow(); 3115 EdgeEffectCompat.onPullDistance(mLeftGlow, -overscrollX / getWidth(), 3116 1f - y / getHeight()); 3117 if (ev != null) { 3118 getScrollFeedbackProvider().onScrollLimit( 3119 ev.getDeviceId(), ev.getSource(), horizontalAxis, /* isStart= */ true); 3120 } 3121 invalidate = true; 3122 } else if (overscrollX > 0) { 3123 ensureRightGlow(); 3124 EdgeEffectCompat.onPullDistance(mRightGlow, overscrollX / getWidth(), y / getHeight()); 3125 if (ev != null) { 3126 getScrollFeedbackProvider().onScrollLimit( 3127 ev.getDeviceId(), ev.getSource(), horizontalAxis, /* isStart= */ false); 3128 } 3129 invalidate = true; 3130 } 3131 3132 if (overscrollY < 0) { 3133 ensureTopGlow(); 3134 EdgeEffectCompat.onPullDistance(mTopGlow, -overscrollY / getHeight(), x / getWidth()); 3135 if (ev != null) { 3136 getScrollFeedbackProvider().onScrollLimit( 3137 ev.getDeviceId(), ev.getSource(), verticalAxis, /* isStart= */ true); 3138 } 3139 invalidate = true; 3140 } else if (overscrollY > 0) { 3141 ensureBottomGlow(); 3142 EdgeEffectCompat.onPullDistance(mBottomGlow, overscrollY / getHeight(), 3143 1f - x / getWidth()); 3144 if (ev != null) { 3145 getScrollFeedbackProvider().onScrollLimit( 3146 ev.getDeviceId(), ev.getSource(), verticalAxis, /* isStart= */ false); 3147 } 3148 invalidate = true; 3149 } 3150 3151 if (invalidate || overscrollX != 0 || overscrollY != 0) { 3152 postInvalidateOnAnimation(); 3153 } 3154 } 3155 releaseGlows()3156 private void releaseGlows() { 3157 boolean needsInvalidate = false; 3158 if (mLeftGlow != null) { 3159 mLeftGlow.onRelease(); 3160 needsInvalidate = mLeftGlow.isFinished(); 3161 } 3162 if (mTopGlow != null) { 3163 mTopGlow.onRelease(); 3164 needsInvalidate |= mTopGlow.isFinished(); 3165 } 3166 if (mRightGlow != null) { 3167 mRightGlow.onRelease(); 3168 needsInvalidate |= mRightGlow.isFinished(); 3169 } 3170 if (mBottomGlow != null) { 3171 mBottomGlow.onRelease(); 3172 needsInvalidate |= mBottomGlow.isFinished(); 3173 } 3174 if (needsInvalidate) { 3175 postInvalidateOnAnimation(); 3176 } 3177 } 3178 considerReleasingGlowsOnScroll(int dx, int dy)3179 void considerReleasingGlowsOnScroll(int dx, int dy) { 3180 boolean needsInvalidate = false; 3181 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { 3182 mLeftGlow.onRelease(); 3183 needsInvalidate = mLeftGlow.isFinished(); 3184 } 3185 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { 3186 mRightGlow.onRelease(); 3187 needsInvalidate |= mRightGlow.isFinished(); 3188 } 3189 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { 3190 mTopGlow.onRelease(); 3191 needsInvalidate |= mTopGlow.isFinished(); 3192 } 3193 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { 3194 mBottomGlow.onRelease(); 3195 needsInvalidate |= mBottomGlow.isFinished(); 3196 } 3197 if (needsInvalidate) { 3198 postInvalidateOnAnimation(); 3199 } 3200 } 3201 absorbGlows(int velocityX, int velocityY)3202 void absorbGlows(int velocityX, int velocityY) { 3203 if (velocityX < 0) { 3204 ensureLeftGlow(); 3205 if (mLeftGlow.isFinished()) { 3206 mLeftGlow.onAbsorb(-velocityX); 3207 } 3208 } else if (velocityX > 0) { 3209 ensureRightGlow(); 3210 if (mRightGlow.isFinished()) { 3211 mRightGlow.onAbsorb(velocityX); 3212 } 3213 } 3214 3215 if (velocityY < 0) { 3216 ensureTopGlow(); 3217 if (mTopGlow.isFinished()) { 3218 mTopGlow.onAbsorb(-velocityY); 3219 } 3220 } else if (velocityY > 0) { 3221 ensureBottomGlow(); 3222 if (mBottomGlow.isFinished()) { 3223 mBottomGlow.onAbsorb(velocityY); 3224 } 3225 } 3226 3227 if (velocityX != 0 || velocityY != 0) { 3228 postInvalidateOnAnimation(); 3229 } 3230 } 3231 ensureLeftGlow()3232 void ensureLeftGlow() { 3233 if (mLeftGlow != null) { 3234 return; 3235 } 3236 mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT); 3237 if (mClipToPadding) { 3238 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 3239 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 3240 } else { 3241 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 3242 } 3243 } 3244 ensureRightGlow()3245 void ensureRightGlow() { 3246 if (mRightGlow != null) { 3247 return; 3248 } 3249 mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT); 3250 if (mClipToPadding) { 3251 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 3252 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 3253 } else { 3254 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); 3255 } 3256 } 3257 ensureTopGlow()3258 void ensureTopGlow() { 3259 if (mTopGlow != null) { 3260 return; 3261 } 3262 mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP); 3263 if (mClipToPadding) { 3264 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 3265 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 3266 } else { 3267 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 3268 } 3269 3270 } 3271 ensureBottomGlow()3272 void ensureBottomGlow() { 3273 if (mBottomGlow != null) { 3274 return; 3275 } 3276 mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM); 3277 if (mClipToPadding) { 3278 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 3279 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 3280 } else { 3281 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); 3282 } 3283 } 3284 invalidateGlows()3285 void invalidateGlows() { 3286 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; 3287 } 3288 3289 /** 3290 * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}. 3291 * <p> 3292 * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared 3293 * and new effects are created as needed using 3294 * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)} 3295 * 3296 * @param edgeEffectFactory The {@link EdgeEffectFactory} instance. 3297 */ setEdgeEffectFactory(@onNull EdgeEffectFactory edgeEffectFactory)3298 public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) { 3299 Preconditions.checkNotNull(edgeEffectFactory); 3300 mEdgeEffectFactory = edgeEffectFactory; 3301 invalidateGlows(); 3302 } 3303 3304 /** 3305 * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing 3306 * was set. 3307 * 3308 * @return The previously set {@link EdgeEffectFactory} 3309 * @see #setEdgeEffectFactory(EdgeEffectFactory) 3310 */ getEdgeEffectFactory()3311 public @NonNull EdgeEffectFactory getEdgeEffectFactory() { 3312 return mEdgeEffectFactory; 3313 } 3314 3315 /** 3316 * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are 3317 * in the Adapter but not visible in the UI), it employs a more involved focus search strategy 3318 * that differs from other ViewGroups. 3319 * <p> 3320 * It first does a focus search within the RecyclerView. If this search finds a View that is in 3321 * the focus direction with respect to the currently focused View, RecyclerView returns that 3322 * child as the next focus target. When it cannot find such child, it calls 3323 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views 3324 * in the focus search direction. If LayoutManager adds a View that matches the 3325 * focus search criteria, it will be returned as the focus search result. Otherwise, 3326 * RecyclerView will call parent to handle the focus search like a regular ViewGroup. 3327 * <p> 3328 * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that 3329 * is not in the focus direction is still valid focus target which may not be the desired 3330 * behavior if the Adapter has more children in the focus direction. To handle this case, 3331 * RecyclerView converts the focus direction to an absolute direction and makes a preliminary 3332 * focus search in that direction. If there are no Views to gain focus, it will call 3333 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a 3334 * focus search with the original (relative) direction. This allows RecyclerView to provide 3335 * better candidates to the focus search while still allowing the view system to take focus from 3336 * the RecyclerView and give it to a more suitable child if such child exists. 3337 * 3338 * @param focused The view that currently has focus 3339 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 3340 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 3341 * {@link View#FOCUS_FORWARD}, 3342 * {@link View#FOCUS_BACKWARD} or 0 for not applicable. 3343 * @return A new View that can be the next focus after the focused View 3344 */ 3345 @Override focusSearch(View focused, int direction)3346 public View focusSearch(View focused, int direction) { 3347 View result = mLayout.onInterceptFocusSearch(focused, direction); 3348 if (result != null) { 3349 return result; 3350 } 3351 final boolean canRunFocusFailure = mAdapter != null && mLayout != null 3352 && !isComputingLayout() && !mLayoutSuppressed; 3353 3354 final FocusFinder ff = FocusFinder.getInstance(); 3355 if (canRunFocusFailure 3356 && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { 3357 // convert direction to absolute direction and see if we have a view there and if not 3358 // tell LayoutManager to add if it can. 3359 boolean needsFocusFailureLayout = false; 3360 if (mLayout.canScrollVertically()) { 3361 final int absDir = 3362 direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; 3363 final View found = ff.findNextFocus(this, focused, absDir); 3364 needsFocusFailureLayout = found == null; 3365 } 3366 if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { 3367 boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 3368 final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl 3369 ? View.FOCUS_RIGHT : View.FOCUS_LEFT; 3370 final View found = ff.findNextFocus(this, focused, absDir); 3371 needsFocusFailureLayout = found == null; 3372 } 3373 if (needsFocusFailureLayout) { 3374 consumePendingUpdateOperations(); 3375 final View focusedItemView = findContainingItemView(focused); 3376 if (focusedItemView == null) { 3377 // panic, focused view is not a child anymore, cannot call super. 3378 return null; 3379 } 3380 startInterceptRequestLayout(); 3381 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 3382 stopInterceptRequestLayout(false); 3383 } 3384 result = ff.findNextFocus(this, focused, direction); 3385 } else { 3386 result = ff.findNextFocus(this, focused, direction); 3387 if (result == null && canRunFocusFailure) { 3388 consumePendingUpdateOperations(); 3389 final View focusedItemView = findContainingItemView(focused); 3390 if (focusedItemView == null) { 3391 // panic, focused view is not a child anymore, cannot call super. 3392 return null; 3393 } 3394 startInterceptRequestLayout(); 3395 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); 3396 stopInterceptRequestLayout(false); 3397 } 3398 } 3399 if (result != null && !result.hasFocusable()) { 3400 if (getFocusedChild() == null) { 3401 // Scrolling to this unfocusable view is not meaningful since there is no currently 3402 // focused view which RV needs to keep visible. 3403 return super.focusSearch(focused, direction); 3404 } 3405 // If the next view returned by onFocusSearchFailed in layout manager has no focusable 3406 // views, we still scroll to that view in order to make it visible on the screen. 3407 // If it's focusable, framework already calls RV's requestChildFocus which handles 3408 // bringing this newly focused item onto the screen. 3409 requestChildOnScreen(result, null); 3410 return focused; 3411 } 3412 return isPreferredNextFocus(focused, result, direction) 3413 ? result : super.focusSearch(focused, direction); 3414 } 3415 3416 /** 3417 * Checks if the new focus candidate is a good enough candidate such that RecyclerView will 3418 * assign it as the next focus View instead of letting view hierarchy decide. 3419 * A good candidate means a View that is aligned in the focus direction wrt the focused View 3420 * and is not the RecyclerView itself. 3421 * When this method returns false, RecyclerView will let the parent make the decision so the 3422 * same View may still get the focus as a result of that search. 3423 */ isPreferredNextFocus(View focused, View next, int direction)3424 private boolean isPreferredNextFocus(View focused, View next, int direction) { 3425 if (next == null || next == this || next == focused) { 3426 return false; 3427 } 3428 // panic, result view is not a child anymore, maybe workaround b/37864393 3429 if (findContainingItemView(next) == null) { 3430 return false; 3431 } 3432 if (focused == null) { 3433 return true; 3434 } 3435 // panic, focused view is not a child anymore, maybe workaround b/37864393 3436 if (findContainingItemView(focused) == null) { 3437 return true; 3438 } 3439 3440 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 3441 mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); 3442 offsetDescendantRectToMyCoords(focused, mTempRect); 3443 offsetDescendantRectToMyCoords(next, mTempRect2); 3444 final int rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -1 : 1; 3445 int rightness = 0; 3446 if ((mTempRect.left < mTempRect2.left 3447 || mTempRect.right <= mTempRect2.left) 3448 && mTempRect.right < mTempRect2.right) { 3449 rightness = 1; 3450 } else if ((mTempRect.right > mTempRect2.right 3451 || mTempRect.left >= mTempRect2.right) 3452 && mTempRect.left > mTempRect2.left) { 3453 rightness = -1; 3454 } 3455 int downness = 0; 3456 if ((mTempRect.top < mTempRect2.top 3457 || mTempRect.bottom <= mTempRect2.top) 3458 && mTempRect.bottom < mTempRect2.bottom) { 3459 downness = 1; 3460 } else if ((mTempRect.bottom > mTempRect2.bottom 3461 || mTempRect.top >= mTempRect2.bottom) 3462 && mTempRect.top > mTempRect2.top) { 3463 downness = -1; 3464 } 3465 switch (direction) { 3466 case View.FOCUS_LEFT: 3467 return rightness < 0; 3468 case View.FOCUS_RIGHT: 3469 return rightness > 0; 3470 case View.FOCUS_UP: 3471 return downness < 0; 3472 case View.FOCUS_DOWN: 3473 return downness > 0; 3474 case View.FOCUS_FORWARD: 3475 return downness > 0 || (downness == 0 && rightness * rtl > 0); 3476 case View.FOCUS_BACKWARD: 3477 return downness < 0 || (downness == 0 && rightness * rtl < 0); 3478 } 3479 throw new IllegalArgumentException("Invalid direction: " + direction + exceptionLabel()); 3480 } 3481 3482 @Override requestChildFocus(View child, View focused)3483 public void requestChildFocus(View child, View focused) { 3484 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { 3485 requestChildOnScreen(child, focused); 3486 } 3487 super.requestChildFocus(child, focused); 3488 } 3489 3490 /** 3491 * Requests that the given child of the RecyclerView be positioned onto the screen. This method 3492 * can be called for both unfocusable and focusable child views. For unfocusable child views, 3493 * the {@param focused} parameter passed is null, whereas for a focusable child, this parameter 3494 * indicates the actual descendant view within this child view that holds the focus. 3495 * 3496 * @param child The child view of this RecyclerView that wants to come onto the screen. 3497 * @param focused The descendant view that actually has the focus if child is focusable, null 3498 * otherwise. 3499 */ requestChildOnScreen(@onNull View child, @Nullable View focused)3500 private void requestChildOnScreen(@NonNull View child, @Nullable View focused) { 3501 View rectView = (focused != null) ? focused : child; 3502 mTempRect.set(0, 0, rectView.getWidth(), rectView.getHeight()); 3503 3504 // get item decor offsets w/o refreshing. If they are invalid, there will be another 3505 // layout pass to fix them, then it is LayoutManager's responsibility to keep focused 3506 // View in viewport. 3507 final ViewGroup.LayoutParams focusedLayoutParams = rectView.getLayoutParams(); 3508 if (focusedLayoutParams instanceof LayoutParams) { 3509 // if focused child has item decors, use them. Otherwise, ignore. 3510 final LayoutParams lp = (LayoutParams) focusedLayoutParams; 3511 if (!lp.mInsetsDirty) { 3512 final Rect insets = lp.mDecorInsets; 3513 mTempRect.left -= insets.left; 3514 mTempRect.right += insets.right; 3515 mTempRect.top -= insets.top; 3516 mTempRect.bottom += insets.bottom; 3517 } 3518 } 3519 3520 if (focused != null) { 3521 offsetDescendantRectToMyCoords(focused, mTempRect); 3522 offsetRectIntoDescendantCoords(child, mTempRect); 3523 } 3524 mLayout.requestChildRectangleOnScreen(this, child, mTempRect, !mFirstLayoutComplete, 3525 (focused == null)); 3526 } 3527 3528 @Override requestChildRectangleOnScreen(View child, Rect rect, boolean immediate)3529 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 3530 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); 3531 } 3532 3533 @Override addFocusables(ArrayList<View> views, int direction, int focusableMode)3534 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 3535 if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { 3536 super.addFocusables(views, direction, focusableMode); 3537 } 3538 } 3539 3540 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)3541 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 3542 if (isComputingLayout()) { 3543 // if we are in the middle of a layout calculation, don't let any child take focus. 3544 // RV will handle it after layout calculation is finished. 3545 return false; 3546 } 3547 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 3548 } 3549 3550 @Override onAttachedToWindow()3551 protected void onAttachedToWindow() { 3552 super.onAttachedToWindow(); 3553 mLayoutOrScrollCounter = 0; 3554 mIsAttached = true; 3555 mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); 3556 3557 mRecycler.onAttachedToWindow(); 3558 3559 if (mLayout != null) { 3560 mLayout.dispatchAttachedToWindow(this); 3561 } 3562 mPostedAnimatorRunner = false; 3563 3564 if (ALLOW_THREAD_GAP_WORK) { 3565 // Register with gap worker 3566 mGapWorker = GapWorker.sGapWorker.get(); 3567 if (mGapWorker == null) { 3568 mGapWorker = new GapWorker(); 3569 3570 // break 60 fps assumption if data from display appears valid 3571 // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) 3572 Display display = ViewCompat.getDisplay(this); 3573 float refreshRate = 60.0f; 3574 if (!isInEditMode() && display != null) { 3575 float displayRefreshRate = display.getRefreshRate(); 3576 if (displayRefreshRate >= 30.0f) { 3577 refreshRate = displayRefreshRate; 3578 } 3579 } 3580 mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); 3581 GapWorker.sGapWorker.set(mGapWorker); 3582 } 3583 mGapWorker.add(this); 3584 } 3585 } 3586 3587 @Override onDetachedFromWindow()3588 protected void onDetachedFromWindow() { 3589 super.onDetachedFromWindow(); 3590 if (mItemAnimator != null) { 3591 mItemAnimator.endAnimations(); 3592 } 3593 stopScroll(); 3594 mIsAttached = false; 3595 if (mLayout != null) { 3596 mLayout.dispatchDetachedFromWindow(this, mRecycler); 3597 } 3598 mPendingAccessibilityImportanceChange.clear(); 3599 removeCallbacks(mItemAnimatorRunner); 3600 mViewInfoStore.onDetach(); 3601 mRecycler.onDetachedFromWindow(); 3602 3603 PoolingContainer.callPoolingContainerOnReleaseForChildren(this); 3604 3605 if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) { 3606 // Unregister with gap worker 3607 mGapWorker.remove(this); 3608 mGapWorker = null; 3609 } 3610 } 3611 3612 /** 3613 * Returns true if RecyclerView is attached to window. 3614 */ 3615 @Override isAttachedToWindow()3616 public boolean isAttachedToWindow() { 3617 return mIsAttached; 3618 } 3619 3620 /** 3621 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 3622 * {@link IllegalStateException} if it <b>is not</b>. 3623 * 3624 * @param message The message for the exception. Can be null. 3625 * @see #assertNotInLayoutOrScroll(String) 3626 */ assertInLayoutOrScroll(String message)3627 void assertInLayoutOrScroll(String message) { 3628 if (!isComputingLayout()) { 3629 if (message == null) { 3630 throw new IllegalStateException("Cannot call this method unless RecyclerView is " 3631 + "computing a layout or scrolling" + exceptionLabel()); 3632 } 3633 throw new IllegalStateException(message + exceptionLabel()); 3634 3635 } 3636 } 3637 3638 /** 3639 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 3640 * {@link IllegalStateException} if it <b>is</b>. 3641 * 3642 * @param message The message for the exception. Can be null. 3643 * @see #assertInLayoutOrScroll(String) 3644 */ assertNotInLayoutOrScroll(String message)3645 void assertNotInLayoutOrScroll(String message) { 3646 if (isComputingLayout()) { 3647 if (message == null) { 3648 throw new IllegalStateException("Cannot call this method while RecyclerView is " 3649 + "computing a layout or scrolling" + exceptionLabel()); 3650 } 3651 throw new IllegalStateException(message); 3652 } 3653 if (mDispatchScrollCounter > 0) { 3654 Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might" 3655 + "be run during a measure & layout pass where you cannot change the" 3656 + "RecyclerView data. Any method call that might change the structure" 3657 + "of the RecyclerView or the adapter contents should be postponed to" 3658 + "the next frame.", 3659 new IllegalStateException("" + exceptionLabel())); 3660 } 3661 } 3662 3663 /** 3664 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 3665 * to child views or this view's standard scrolling behavior. 3666 * 3667 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 3668 * returns true from 3669 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 3670 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 3671 * for each incoming MotionEvent until the end of the gesture.</p> 3672 * 3673 * @param listener Listener to add 3674 * @see SimpleOnItemTouchListener 3675 */ addOnItemTouchListener(@onNull OnItemTouchListener listener)3676 public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) { 3677 mOnItemTouchListeners.add(listener); 3678 } 3679 3680 /** 3681 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 3682 * 3683 * @param listener Listener to remove 3684 */ removeOnItemTouchListener(@onNull OnItemTouchListener listener)3685 public void removeOnItemTouchListener(@NonNull OnItemTouchListener listener) { 3686 mOnItemTouchListeners.remove(listener); 3687 if (mInterceptingOnItemTouchListener == listener) { 3688 mInterceptingOnItemTouchListener = null; 3689 } 3690 } 3691 3692 /** 3693 * Dispatches the motion event to the intercepting OnItemTouchListener or provides opportunity 3694 * for OnItemTouchListeners to intercept. 3695 * 3696 * @param e The MotionEvent 3697 * @return True if handled by an intercepting OnItemTouchListener. 3698 */ dispatchToOnItemTouchListeners(MotionEvent e)3699 private boolean dispatchToOnItemTouchListeners(MotionEvent e) { 3700 3701 // OnItemTouchListeners should receive calls to their methods in the same pattern that 3702 // ViewGroups do. That pattern is a bit confusing, which in turn makes the below code a 3703 // bit confusing. Here are rules for the pattern: 3704 // 3705 // 1. A single MotionEvent should not be passed to either OnInterceptTouchEvent or 3706 // OnTouchEvent twice. 3707 // 2. ACTION_DOWN MotionEvents may be passed to both onInterceptTouchEvent and 3708 // onTouchEvent. 3709 // 3. All other MotionEvents should be passed to either onInterceptTouchEvent or 3710 // onTouchEvent, not both. 3711 3712 // Side Note: We don't currently perfectly mimic how MotionEvents work in the view system. 3713 // If we were to do so, for every MotionEvent, any OnItemTouchListener that is before the 3714 // intercepting OnItemTouchEvent should still have a chance to intercept, and if it does, 3715 // the previously intercepting OnItemTouchEvent should get an ACTION_CANCEL event. 3716 3717 if (mInterceptingOnItemTouchListener == null) { 3718 if (e.getAction() == MotionEvent.ACTION_DOWN) { 3719 return false; 3720 } 3721 return findInterceptingOnItemTouchListener(e); 3722 } else { 3723 mInterceptingOnItemTouchListener.onTouchEvent(this, e); 3724 final int action = e.getAction(); 3725 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 3726 mInterceptingOnItemTouchListener = null; 3727 } 3728 return true; 3729 } 3730 } 3731 3732 /** 3733 * Looks for an OnItemTouchListener that wants to intercept. 3734 * 3735 * <p>Calls {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} on each 3736 * of the registered {@link OnItemTouchListener}s, passing in the 3737 * MotionEvent. If one returns true and the action is not ACTION_CANCEL, saves the intercepting 3738 * OnItemTouchListener to be called for future {@link RecyclerView#onTouchEvent(MotionEvent)} 3739 * and immediately returns true. If none want to intercept or the action is ACTION_CANCEL, 3740 * returns false. 3741 * 3742 * @param e The MotionEvent 3743 * @return true if an OnItemTouchListener is saved as intercepting. 3744 */ findInterceptingOnItemTouchListener(MotionEvent e)3745 private boolean findInterceptingOnItemTouchListener(MotionEvent e) { 3746 int action = e.getAction(); 3747 final int listenerCount = mOnItemTouchListeners.size(); 3748 for (int i = 0; i < listenerCount; i++) { 3749 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 3750 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 3751 mInterceptingOnItemTouchListener = listener; 3752 return true; 3753 } 3754 } 3755 return false; 3756 } 3757 3758 @Override onInterceptTouchEvent(MotionEvent e)3759 public boolean onInterceptTouchEvent(MotionEvent e) { 3760 if (mLayoutSuppressed) { 3761 // When layout is suppressed, RV does not intercept the motion event. 3762 // A child view e.g. a button may still get the click. 3763 return false; 3764 } 3765 3766 // Clear the active onInterceptTouchListener. None should be set at this time, and if one 3767 // is, it's because some other code didn't follow the standard contract. 3768 mInterceptingOnItemTouchListener = null; 3769 if (findInterceptingOnItemTouchListener(e)) { 3770 cancelScroll(); 3771 MotionEvent cancelEvent = MotionEvent.obtain(e); 3772 cancelEvent.setAction(MotionEvent.ACTION_CANCEL); 3773 final int listenerCount = mOnItemTouchListeners.size(); 3774 for (int i = 0; i < listenerCount; i++) { 3775 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 3776 if (listener == null || listener == mInterceptingOnItemTouchListener) { 3777 continue; 3778 } else { 3779 listener.onInterceptTouchEvent(this, cancelEvent); 3780 } 3781 } 3782 return true; 3783 } 3784 3785 if (mLayout == null) { 3786 return false; 3787 } 3788 3789 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 3790 final boolean canScrollVertically = mLayout.canScrollVertically(); 3791 3792 if (mVelocityTracker == null) { 3793 mVelocityTracker = VelocityTracker.obtain(); 3794 } 3795 mVelocityTracker.addMovement(e); 3796 3797 final int action = e.getActionMasked(); 3798 final int actionIndex = e.getActionIndex(); 3799 3800 switch (action) { 3801 case MotionEvent.ACTION_DOWN: 3802 if (mIgnoreMotionEventTillDown) { 3803 mIgnoreMotionEventTillDown = false; 3804 } 3805 mScrollPointerId = e.getPointerId(0); 3806 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 3807 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 3808 3809 if (stopGlowAnimations(e) || mScrollState == SCROLL_STATE_SETTLING) { 3810 getParent().requestDisallowInterceptTouchEvent(true); 3811 setScrollState(SCROLL_STATE_DRAGGING); 3812 stopNestedScroll(TYPE_NON_TOUCH); 3813 } 3814 3815 // Clear the nested offsets 3816 mNestedOffsets[0] = mNestedOffsets[1] = 0; 3817 3818 startNestedScrollForType(TYPE_TOUCH); 3819 break; 3820 3821 case MotionEvent.ACTION_POINTER_DOWN: 3822 mScrollPointerId = e.getPointerId(actionIndex); 3823 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 3824 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 3825 break; 3826 3827 case MotionEvent.ACTION_MOVE: { 3828 final int index = e.findPointerIndex(mScrollPointerId); 3829 if (index < 0) { 3830 Log.e(TAG, "Error processing scroll; pointer index for id " 3831 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 3832 return false; 3833 } 3834 3835 final int x = (int) (e.getX(index) + 0.5f); 3836 final int y = (int) (e.getY(index) + 0.5f); 3837 if (mScrollState != SCROLL_STATE_DRAGGING) { 3838 final int dx = x - mInitialTouchX; 3839 final int dy = y - mInitialTouchY; 3840 boolean startScroll = false; 3841 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 3842 mLastTouchX = x; 3843 startScroll = true; 3844 } 3845 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 3846 mLastTouchY = y; 3847 startScroll = true; 3848 } 3849 if (startScroll) { 3850 setScrollState(SCROLL_STATE_DRAGGING); 3851 } 3852 } 3853 } 3854 break; 3855 3856 case MotionEvent.ACTION_POINTER_UP: { 3857 onPointerUp(e); 3858 } 3859 break; 3860 3861 case MotionEvent.ACTION_UP: { 3862 mVelocityTracker.clear(); 3863 stopNestedScroll(TYPE_TOUCH); 3864 } 3865 break; 3866 3867 case MotionEvent.ACTION_CANCEL: { 3868 cancelScroll(); 3869 } 3870 } 3871 return mScrollState == SCROLL_STATE_DRAGGING; 3872 } 3873 3874 /** 3875 * This stops any edge glow animation that is currently running by applying a 3876 * 0 length pull at the displacement given by the provided MotionEvent. On pre-S devices, 3877 * this method does nothing, allowing any animating edge effect to continue animating and 3878 * returning <code>false</code> always. 3879 * 3880 * @param e The motion event to use to indicate the finger position for the displacement of 3881 * the current pull. 3882 * @return <code>true</code> if any edge effect had an existing effect to be drawn ond the 3883 * animation was stopped or <code>false</code> if no edge effect had a value to display. 3884 */ stopGlowAnimations(MotionEvent e)3885 private boolean stopGlowAnimations(MotionEvent e) { 3886 boolean stopped = false; 3887 if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0 3888 && !canScrollHorizontally(-1)) { 3889 EdgeEffectCompat.onPullDistance(mLeftGlow, 0, 1 - (e.getY() / getHeight())); 3890 stopped = true; 3891 } 3892 if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0 3893 && !canScrollHorizontally(1)) { 3894 EdgeEffectCompat.onPullDistance(mRightGlow, 0, e.getY() / getHeight()); 3895 stopped = true; 3896 } 3897 if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0 3898 && !canScrollVertically(-1)) { 3899 EdgeEffectCompat.onPullDistance(mTopGlow, 0, e.getX() / getWidth()); 3900 stopped = true; 3901 } 3902 if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0 3903 && !canScrollVertically(1)) { 3904 EdgeEffectCompat.onPullDistance(mBottomGlow, 0, 1 - e.getX() / getWidth()); 3905 stopped = true; 3906 } 3907 return stopped; 3908 } 3909 3910 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)3911 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 3912 final int listenerCount = mOnItemTouchListeners.size(); 3913 for (int i = 0; i < listenerCount; i++) { 3914 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 3915 listener.onRequestDisallowInterceptTouchEvent(disallowIntercept); 3916 } 3917 super.requestDisallowInterceptTouchEvent(disallowIntercept); 3918 } 3919 3920 @Override onTouchEvent(MotionEvent e)3921 public boolean onTouchEvent(MotionEvent e) { 3922 if (mLayoutSuppressed || mIgnoreMotionEventTillDown) { 3923 return false; 3924 } 3925 if (dispatchToOnItemTouchListeners(e)) { 3926 cancelScroll(); 3927 return true; 3928 } 3929 3930 if (mLayout == null) { 3931 return false; 3932 } 3933 3934 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 3935 final boolean canScrollVertically = mLayout.canScrollVertically(); 3936 3937 if (mVelocityTracker == null) { 3938 mVelocityTracker = VelocityTracker.obtain(); 3939 } 3940 boolean eventAddedToVelocityTracker = false; 3941 3942 final int action = e.getActionMasked(); 3943 final int actionIndex = e.getActionIndex(); 3944 3945 if (action == MotionEvent.ACTION_DOWN) { 3946 mNestedOffsets[0] = mNestedOffsets[1] = 0; 3947 } 3948 final MotionEvent vtev = MotionEvent.obtain(e); 3949 vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); 3950 3951 switch (action) { 3952 case MotionEvent.ACTION_DOWN: { 3953 mScrollPointerId = e.getPointerId(0); 3954 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 3955 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 3956 3957 startNestedScrollForType(TYPE_TOUCH); 3958 } 3959 break; 3960 3961 case MotionEvent.ACTION_POINTER_DOWN: { 3962 mScrollPointerId = e.getPointerId(actionIndex); 3963 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); 3964 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); 3965 } 3966 break; 3967 3968 case MotionEvent.ACTION_MOVE: { 3969 final int index = e.findPointerIndex(mScrollPointerId); 3970 if (index < 0) { 3971 Log.e(TAG, "Error processing scroll; pointer index for id " 3972 + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 3973 return false; 3974 } 3975 3976 final int x = (int) (e.getX(index) + 0.5f); 3977 final int y = (int) (e.getY(index) + 0.5f); 3978 int dx = mLastTouchX - x; 3979 int dy = mLastTouchY - y; 3980 3981 if (mScrollState != SCROLL_STATE_DRAGGING) { 3982 boolean startScroll = false; 3983 if (canScrollHorizontally) { 3984 if (dx > 0) { 3985 dx = Math.max(0, dx - mTouchSlop); 3986 } else { 3987 dx = Math.min(0, dx + mTouchSlop); 3988 } 3989 if (dx != 0) { 3990 startScroll = true; 3991 } 3992 } 3993 if (canScrollVertically) { 3994 if (dy > 0) { 3995 dy = Math.max(0, dy - mTouchSlop); 3996 } else { 3997 dy = Math.min(0, dy + mTouchSlop); 3998 } 3999 if (dy != 0) { 4000 startScroll = true; 4001 } 4002 } 4003 if (startScroll) { 4004 setScrollState(SCROLL_STATE_DRAGGING); 4005 } 4006 } 4007 4008 if (mScrollState == SCROLL_STATE_DRAGGING) { 4009 mReusableIntPair[0] = 0; 4010 mReusableIntPair[1] = 0; 4011 dx -= releaseHorizontalGlow(dx, e.getY()); 4012 dy -= releaseVerticalGlow(dy, e.getX()); 4013 4014 if (dispatchNestedPreScroll( 4015 canScrollHorizontally ? dx : 0, 4016 canScrollVertically ? dy : 0, 4017 mReusableIntPair, mScrollOffset, TYPE_TOUCH 4018 )) { 4019 dx -= mReusableIntPair[0]; 4020 dy -= mReusableIntPair[1]; 4021 // Updated the nested offsets 4022 mNestedOffsets[0] += mScrollOffset[0]; 4023 mNestedOffsets[1] += mScrollOffset[1]; 4024 // Scroll has initiated, prevent parents from intercepting 4025 getParent().requestDisallowInterceptTouchEvent(true); 4026 } 4027 4028 mLastTouchX = x - mScrollOffset[0]; 4029 mLastTouchY = y - mScrollOffset[1]; 4030 4031 if (scrollByInternal( 4032 canScrollHorizontally ? dx : 0, 4033 canScrollVertically ? dy : 0, 4034 MotionEvent.AXIS_X, 4035 MotionEvent.AXIS_Y, 4036 e, TYPE_TOUCH)) { 4037 getParent().requestDisallowInterceptTouchEvent(true); 4038 } 4039 if (mGapWorker != null && (dx != 0 || dy != 0)) { 4040 mGapWorker.postFromTraversal(this, dx, dy); 4041 } 4042 } 4043 } 4044 break; 4045 4046 case MotionEvent.ACTION_POINTER_UP: { 4047 onPointerUp(e); 4048 } 4049 break; 4050 4051 case MotionEvent.ACTION_UP: { 4052 mVelocityTracker.addMovement(vtev); 4053 eventAddedToVelocityTracker = true; 4054 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 4055 final float xvel = canScrollHorizontally 4056 ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; 4057 final float yvel = canScrollVertically 4058 ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; 4059 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 4060 setScrollState(SCROLL_STATE_IDLE); 4061 } 4062 resetScroll(); 4063 } 4064 break; 4065 4066 case MotionEvent.ACTION_CANCEL: { 4067 cancelScroll(); 4068 } 4069 break; 4070 } 4071 4072 if (!eventAddedToVelocityTracker) { 4073 mVelocityTracker.addMovement(vtev); 4074 } 4075 vtev.recycle(); 4076 4077 return true; 4078 } 4079 resetScroll()4080 private void resetScroll() { 4081 if (mVelocityTracker != null) { 4082 mVelocityTracker.clear(); 4083 } 4084 stopNestedScroll(TYPE_TOUCH); 4085 releaseGlows(); 4086 } 4087 cancelScroll()4088 private void cancelScroll() { 4089 resetScroll(); 4090 setScrollState(SCROLL_STATE_IDLE); 4091 } 4092 onPointerUp(MotionEvent e)4093 private void onPointerUp(MotionEvent e) { 4094 final int actionIndex = e.getActionIndex(); 4095 if (e.getPointerId(actionIndex) == mScrollPointerId) { 4096 // Pick a new pointer to pick up the slack. 4097 final int newIndex = actionIndex == 0 ? 1 : 0; 4098 mScrollPointerId = e.getPointerId(newIndex); 4099 mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); 4100 mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); 4101 } 4102 } 4103 4104 @Override onGenericMotionEvent(MotionEvent event)4105 public boolean onGenericMotionEvent(MotionEvent event) { 4106 if (mLayout == null) { 4107 return false; 4108 } 4109 if (mLayoutSuppressed) { 4110 return false; 4111 } 4112 4113 int flingAxis = 0; 4114 int horizontalAxis = 0; 4115 int verticalAxis = 0; 4116 boolean useSmoothScroll = false; 4117 if (event.getAction() == MotionEvent.ACTION_SCROLL) { 4118 final float vScroll, hScroll; 4119 if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { 4120 if (mLayout.canScrollVertically()) { 4121 // Inverse the sign of the vertical scroll to align the scroll orientation 4122 // with AbsListView. 4123 vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 4124 verticalAxis = MotionEvent.AXIS_VSCROLL; 4125 } else { 4126 vScroll = 0f; 4127 } 4128 if (mLayout.canScrollHorizontally()) { 4129 hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 4130 horizontalAxis = MotionEvent.AXIS_HSCROLL; 4131 } else { 4132 hScroll = 0f; 4133 } 4134 } else if ((event.getSource() & InputDeviceCompat.SOURCE_ROTARY_ENCODER) != 0) { 4135 final float axisScroll = event.getAxisValue(MotionEventCompat.AXIS_SCROLL); 4136 if (mLayout.canScrollVertically()) { 4137 // Invert the sign of the vertical scroll to align the scroll orientation 4138 // with AbsListView. 4139 vScroll = -axisScroll; 4140 verticalAxis = MotionEvent.AXIS_SCROLL; 4141 hScroll = 0f; 4142 } else if (mLayout.canScrollHorizontally()) { 4143 vScroll = 0f; 4144 hScroll = axisScroll; 4145 horizontalAxis = MotionEvent.AXIS_SCROLL; 4146 } else { 4147 vScroll = 0f; 4148 hScroll = 0f; 4149 } 4150 // Use smooth scrolling for low resolution rotary encoders to avoid the visible 4151 // pixel jumps that would be caused by doing regular scrolling. 4152 useSmoothScroll = mLowResRotaryEncoderFeature; 4153 // Support fling for rotary encoders. 4154 flingAxis = MotionEventCompat.AXIS_SCROLL; 4155 } else { 4156 vScroll = 0f; 4157 hScroll = 0f; 4158 } 4159 4160 int scaledVScroll = (int) (vScroll * mScaledVerticalScrollFactor); 4161 int scaledHScroll = (int) (hScroll * mScaledHorizontalScrollFactor); 4162 if (useSmoothScroll) { 4163 OverScroller overScroller = mViewFlinger.mOverScroller; 4164 // Account for any remaining scroll from a previous generic motion event. 4165 scaledVScroll += overScroller.getFinalY() - overScroller.getCurrY(); 4166 scaledHScroll += overScroller.getFinalX() - overScroller.getCurrX(); 4167 smoothScrollBy(scaledHScroll, scaledVScroll, /* interpolator= */ null, 4168 UNDEFINED_DURATION, /* withNestedScrolling= */ true); 4169 } else { 4170 nestedScrollByInternal(scaledHScroll, scaledVScroll, horizontalAxis, 4171 verticalAxis, event, TYPE_NON_TOUCH); 4172 } 4173 4174 if (flingAxis != 0 && !useSmoothScroll) { 4175 mDifferentialMotionFlingController.onMotionEvent(event, flingAxis); 4176 } 4177 } 4178 return false; 4179 } 4180 4181 @Override onMeasure(int widthSpec, int heightSpec)4182 protected void onMeasure(int widthSpec, int heightSpec) { 4183 if (mLayout == null) { 4184 defaultOnMeasure(widthSpec, heightSpec); 4185 return; 4186 } 4187 if (mLayout.isAutoMeasureEnabled()) { 4188 final int widthMode = MeasureSpec.getMode(widthSpec); 4189 final int heightMode = MeasureSpec.getMode(heightSpec); 4190 4191 /* 4192 * This specific call should be considered deprecated and replaced with 4193 * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could 4194 * break existing third party code but all documentation directs developers to not 4195 * override {@link LayoutManager#onMeasure(int, int)} when 4196 * {@link LayoutManager#isAutoMeasureEnabled()} returns true. 4197 */ 4198 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 4199 4200 // Calculate and track whether we should skip measurement here because the MeasureSpec 4201 // modes in both dimensions are EXACTLY. 4202 mLastAutoMeasureSkippedDueToExact = 4203 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; 4204 if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) { 4205 return; 4206 } 4207 4208 if (mState.mLayoutStep == State.STEP_START) { 4209 dispatchLayoutStep1(); 4210 } 4211 // set dimensions in 2nd step. Pre-layout should happen with old dimensions for 4212 // consistency 4213 mLayout.setMeasureSpecs(widthSpec, heightSpec); 4214 mState.mIsMeasuring = true; 4215 dispatchLayoutStep2(); 4216 4217 // now we can get the width and height from the children. 4218 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 4219 4220 // if RecyclerView has non-exact width and height and if there is at least one child 4221 // which also has non-exact width & height, we have to re-measure. 4222 if (mLayout.shouldMeasureTwice()) { 4223 mLayout.setMeasureSpecs( 4224 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 4225 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 4226 mState.mIsMeasuring = true; 4227 dispatchLayoutStep2(); 4228 // now we can get the width and height from the children. 4229 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); 4230 } 4231 4232 mLastAutoMeasureNonExactMeasuredWidth = getMeasuredWidth(); 4233 mLastAutoMeasureNonExactMeasuredHeight = getMeasuredHeight(); 4234 } else { 4235 if (mHasFixedSize) { 4236 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 4237 return; 4238 } 4239 // custom onMeasure 4240 if (mAdapterUpdateDuringMeasure) { 4241 startInterceptRequestLayout(); 4242 onEnterLayoutOrScroll(); 4243 processAdapterUpdatesAndSetAnimationFlags(); 4244 onExitLayoutOrScroll(); 4245 4246 if (mState.mRunPredictiveAnimations) { 4247 mState.mInPreLayout = true; 4248 } else { 4249 // consume remaining updates to provide a consistent state with the layout pass. 4250 mAdapterHelper.consumeUpdatesInOnePass(); 4251 mState.mInPreLayout = false; 4252 } 4253 mAdapterUpdateDuringMeasure = false; 4254 stopInterceptRequestLayout(false); 4255 } else if (mState.mRunPredictiveAnimations) { 4256 // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: 4257 // this means there is already an onMeasure() call performed to handle the pending 4258 // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout 4259 // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time 4260 // because getViewForPosition() will crash when LM uses a child to measure. 4261 setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); 4262 return; 4263 } 4264 4265 if (mAdapter != null) { 4266 mState.mItemCount = mAdapter.getItemCount(); 4267 } else { 4268 mState.mItemCount = 0; 4269 } 4270 startInterceptRequestLayout(); 4271 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 4272 stopInterceptRequestLayout(false); 4273 mState.mInPreLayout = false; // clear 4274 } 4275 } 4276 4277 /** 4278 * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios 4279 * where this RecyclerView is otherwise lacking better information. 4280 */ defaultOnMeasure(int widthSpec, int heightSpec)4281 void defaultOnMeasure(int widthSpec, int heightSpec) { 4282 // calling LayoutManager here is not pretty but that API is already public and it is better 4283 // than creating another method since this is internal. 4284 final int width = LayoutManager.chooseSize(widthSpec, 4285 getPaddingLeft() + getPaddingRight(), 4286 ViewCompat.getMinimumWidth(this)); 4287 final int height = LayoutManager.chooseSize(heightSpec, 4288 getPaddingTop() + getPaddingBottom(), 4289 ViewCompat.getMinimumHeight(this)); 4290 4291 setMeasuredDimension(width, height); 4292 } 4293 4294 @Override onSizeChanged(int w, int h, int oldw, int oldh)4295 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 4296 super.onSizeChanged(w, h, oldw, oldh); 4297 if (w != oldw || h != oldh) { 4298 invalidateGlows(); 4299 // layout's w/h are updated during measure/layout steps. 4300 } 4301 } 4302 4303 /** 4304 * Sets the {@link ItemAnimator} that will handle animations involving changes 4305 * to the items in this RecyclerView. By default, RecyclerView instantiates and 4306 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are 4307 * enabled for the RecyclerView depends on the ItemAnimator and whether 4308 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() 4309 * supports item animations}. 4310 * 4311 * @param animator The ItemAnimator being set. If null, no animations will occur 4312 * when changes occur to the items in this RecyclerView. 4313 */ setItemAnimator(@ullable ItemAnimator animator)4314 public void setItemAnimator(@Nullable ItemAnimator animator) { 4315 if (mItemAnimator != null) { 4316 mItemAnimator.endAnimations(); 4317 mItemAnimator.setListener(null); 4318 } 4319 mItemAnimator = animator; 4320 if (mItemAnimator != null) { 4321 mItemAnimator.setListener(mItemAnimatorListener); 4322 } 4323 } 4324 onEnterLayoutOrScroll()4325 void onEnterLayoutOrScroll() { 4326 mLayoutOrScrollCounter++; 4327 } 4328 onExitLayoutOrScroll()4329 void onExitLayoutOrScroll() { 4330 onExitLayoutOrScroll(true); 4331 } 4332 onExitLayoutOrScroll(boolean enableChangeEvents)4333 void onExitLayoutOrScroll(boolean enableChangeEvents) { 4334 mLayoutOrScrollCounter--; 4335 if (mLayoutOrScrollCounter < 1) { 4336 if (sDebugAssertionsEnabled && mLayoutOrScrollCounter < 0) { 4337 throw new IllegalStateException("layout or scroll counter cannot go below zero." 4338 + "Some calls are not matching" + exceptionLabel()); 4339 } 4340 mLayoutOrScrollCounter = 0; 4341 if (enableChangeEvents) { 4342 dispatchContentChangedIfNecessary(); 4343 dispatchPendingImportantForAccessibilityChanges(); 4344 } 4345 } 4346 } 4347 isAccessibilityEnabled()4348 boolean isAccessibilityEnabled() { 4349 return mAccessibilityManager != null && mAccessibilityManager.isEnabled(); 4350 } 4351 dispatchContentChangedIfNecessary()4352 private void dispatchContentChangedIfNecessary() { 4353 final int flags = mEatenAccessibilityChangeFlags; 4354 mEatenAccessibilityChangeFlags = 0; 4355 if (flags != 0 && isAccessibilityEnabled()) { 4356 final AccessibilityEvent event = AccessibilityEvent.obtain(); 4357 event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 4358 AccessibilityEventCompat.setContentChangeTypes(event, flags); 4359 sendAccessibilityEventUnchecked(event); 4360 } 4361 } 4362 4363 /** 4364 * Returns whether RecyclerView is currently computing a layout. 4365 * <p> 4366 * If this method returns true, it means that RecyclerView is in a lockdown state and any 4367 * attempt to update adapter contents will result in an exception because adapter contents 4368 * cannot be changed while RecyclerView is trying to compute the layout. 4369 * <p> 4370 * It is very unlikely that your code will be running during this state as it is 4371 * called by the framework when a layout traversal happens or RecyclerView starts to scroll 4372 * in response to system events (touch, accessibility etc). 4373 * <p> 4374 * This case may happen if you have some custom logic to change adapter contents in 4375 * response to a View callback (e.g. focus change callback) which might be triggered during a 4376 * layout calculation. In these cases, you should just postpone the change using a Handler or a 4377 * similar mechanism. 4378 * 4379 * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code> 4380 * otherwise 4381 */ isComputingLayout()4382 public boolean isComputingLayout() { 4383 return mLayoutOrScrollCounter > 0; 4384 } 4385 4386 /** 4387 * Returns true if an accessibility event should not be dispatched now. This happens when an 4388 * accessibility request arrives while RecyclerView does not have a stable state which is very 4389 * hard to handle for a LayoutManager. Instead, this method records necessary information about 4390 * the event and dispatches a window change event after the critical section is finished. 4391 * 4392 * @return True if the accessibility event should be postponed. 4393 */ shouldDeferAccessibilityEvent(AccessibilityEvent event)4394 boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) { 4395 if (isComputingLayout()) { 4396 int type = 0; 4397 if (event != null) { 4398 type = AccessibilityEventCompat.getContentChangeTypes(event); 4399 } 4400 if (type == 0) { 4401 type = AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED; 4402 } 4403 mEatenAccessibilityChangeFlags |= type; 4404 return true; 4405 } 4406 return false; 4407 } 4408 4409 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)4410 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 4411 if (shouldDeferAccessibilityEvent(event)) { 4412 return; 4413 } 4414 super.sendAccessibilityEventUnchecked(event); 4415 } 4416 4417 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)4418 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 4419 onPopulateAccessibilityEvent(event); 4420 return true; 4421 } 4422 4423 /** 4424 * Gets the current ItemAnimator for this RecyclerView. A null return value 4425 * indicates that there is no animator and that item changes will happen without 4426 * any animations. By default, RecyclerView instantiates and 4427 * uses an instance of {@link DefaultItemAnimator}. 4428 * 4429 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur 4430 * when changes occur to the items in this RecyclerView. 4431 */ getItemAnimator()4432 public @Nullable ItemAnimator getItemAnimator() { 4433 return mItemAnimator; 4434 } 4435 4436 /** 4437 * Post a runnable to the next frame to run pending item animations. Only the first such 4438 * request will be posted, governed by the mPostedAnimatorRunner flag. 4439 */ postAnimationRunner()4440 void postAnimationRunner() { 4441 if (!mPostedAnimatorRunner && mIsAttached) { 4442 ViewCompat.postOnAnimation(this, mItemAnimatorRunner); 4443 mPostedAnimatorRunner = true; 4444 } 4445 } 4446 predictiveItemAnimationsEnabled()4447 private boolean predictiveItemAnimationsEnabled() { 4448 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); 4449 } 4450 4451 /** 4452 * Consumes adapter updates and calculates which type of animations we want to run. 4453 * Called in onMeasure and dispatchLayout. 4454 * <p> 4455 * This method may process only the pre-layout state of updates or all of them. 4456 */ processAdapterUpdatesAndSetAnimationFlags()4457 private void processAdapterUpdatesAndSetAnimationFlags() { 4458 if (mDataSetHasChangedAfterLayout) { 4459 // Processing these items have no value since data set changed unexpectedly. 4460 // Instead, we just reset it. 4461 mAdapterHelper.reset(); 4462 if (mDispatchItemsChangedEvent) { 4463 mLayout.onItemsChanged(this); 4464 } 4465 } 4466 // simple animations are a subset of advanced animations (which will cause a 4467 // pre-layout step) 4468 // If layout supports predictive animations, pre-process to decide if we want to run them 4469 if (predictiveItemAnimationsEnabled()) { 4470 mAdapterHelper.preProcess(); 4471 } else { 4472 mAdapterHelper.consumeUpdatesInOnePass(); 4473 } 4474 boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; 4475 mState.mRunSimpleAnimations = mFirstLayoutComplete 4476 && mItemAnimator != null 4477 && (mDataSetHasChangedAfterLayout 4478 || animationTypeSupported 4479 || mLayout.mRequestedSimpleAnimations) 4480 && (!mDataSetHasChangedAfterLayout 4481 || mAdapter.hasStableIds()); 4482 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations 4483 && animationTypeSupported 4484 && !mDataSetHasChangedAfterLayout 4485 && predictiveItemAnimationsEnabled(); 4486 } 4487 4488 /** 4489 * Wrapper around layoutChildren() that handles animating changes caused by layout. 4490 * Animations work on the assumption that there are five different kinds of items 4491 * in play: 4492 * PERSISTENT: items are visible before and after layout 4493 * REMOVED: items were visible before layout and were removed by the app 4494 * ADDED: items did not exist before layout and were added by the app 4495 * DISAPPEARING: items exist in the data set before/after, but changed from 4496 * visible to non-visible in the process of layout (they were moved off 4497 * screen as a side-effect of other changes) 4498 * APPEARING: items exist in the data set before/after, but changed from 4499 * non-visible to visible in the process of layout (they were moved on 4500 * screen as a side-effect of other changes) 4501 * The overall approach figures out what items exist before/after layout and 4502 * infers one of the five above states for each of the items. Then the animations 4503 * are set up accordingly: 4504 * PERSISTENT views are animated via 4505 * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 4506 * DISAPPEARING views are animated via 4507 * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 4508 * APPEARING views are animated via 4509 * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} 4510 * and changed views are animated via 4511 * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. 4512 */ dispatchLayout()4513 void dispatchLayout() { 4514 if (mAdapter == null) { 4515 Log.w(TAG, "No adapter attached; skipping layout"); 4516 // leave the state in START 4517 return; 4518 } 4519 if (mLayout == null) { 4520 Log.e(TAG, "No layout manager attached; skipping layout"); 4521 // leave the state in START 4522 return; 4523 } 4524 mState.mIsMeasuring = false; 4525 4526 // If the last time we measured children in onMeasure, we skipped the measurement and layout 4527 // of RV children because the MeasureSpec in both dimensions was EXACTLY, and current 4528 // dimensions of the RV are not equal to the last measured dimensions of RV, we need to 4529 // measure and layout children one last time. 4530 boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact 4531 && (mLastAutoMeasureNonExactMeasuredWidth != getWidth() 4532 || mLastAutoMeasureNonExactMeasuredHeight != getHeight()); 4533 mLastAutoMeasureNonExactMeasuredWidth = 0; 4534 mLastAutoMeasureNonExactMeasuredHeight = 0; 4535 mLastAutoMeasureSkippedDueToExact = false; 4536 4537 if (mState.mLayoutStep == State.STEP_START) { 4538 dispatchLayoutStep1(); 4539 mLayout.setExactMeasureSpecsFrom(this); 4540 dispatchLayoutStep2(); 4541 } else if (mAdapterHelper.hasUpdates() 4542 || needsRemeasureDueToExactSkip 4543 || mLayout.getWidth() != getWidth() 4544 || mLayout.getHeight() != getHeight()) { 4545 // First 2 steps are done in onMeasure but looks like we have to run again due to 4546 // changed size. 4547 4548 // TODO(shepshapard): Worth a note that I believe 4549 // "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is 4550 // not actually correct, causes unnecessary work to be done, and should be 4551 // removed. Removing causes many tests to fail and I didn't have the time to 4552 // investigate. Just a note for the a future reader or bug fixer. 4553 mLayout.setExactMeasureSpecsFrom(this); 4554 dispatchLayoutStep2(); 4555 } else { 4556 // always make sure we sync them (to ensure mode is exact) 4557 mLayout.setExactMeasureSpecsFrom(this); 4558 } 4559 dispatchLayoutStep3(); 4560 } 4561 saveFocusInfo()4562 private void saveFocusInfo() { 4563 View child = null; 4564 if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { 4565 child = getFocusedChild(); 4566 } 4567 4568 final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); 4569 if (focusedVh == null) { 4570 resetFocusInfo(); 4571 } else { 4572 mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; 4573 // mFocusedItemPosition should hold the current adapter position of the previously 4574 // focused item. If the item is removed, we store the previous adapter position of the 4575 // removed item. 4576 mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION 4577 : (focusedVh.isRemoved() ? focusedVh.mOldPosition 4578 : focusedVh.getAbsoluteAdapterPosition()); 4579 mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); 4580 } 4581 } 4582 resetFocusInfo()4583 private void resetFocusInfo() { 4584 mState.mFocusedItemId = NO_ID; 4585 mState.mFocusedItemPosition = NO_POSITION; 4586 mState.mFocusedSubChildId = View.NO_ID; 4587 } 4588 4589 /** 4590 * Finds the best view candidate to request focus on using mFocusedItemPosition index of the 4591 * previously focused item. It first traverses the adapter forward to find a focusable candidate 4592 * and if no such candidate is found, it reverses the focus search direction for the items 4593 * before the mFocusedItemPosition'th index; 4594 * 4595 * @return The best candidate to request focus on, or null if no such candidate exists. Null 4596 * indicates all the existing adapter items are unfocusable. 4597 */ findNextViewToFocus()4598 private @Nullable View findNextViewToFocus() { 4599 int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition 4600 : 0; 4601 ViewHolder nextFocus; 4602 final int itemCount = mState.getItemCount(); 4603 for (int i = startFocusSearchIndex; i < itemCount; i++) { 4604 nextFocus = findViewHolderForAdapterPosition(i); 4605 if (nextFocus == null) { 4606 break; 4607 } 4608 if (nextFocus.itemView.hasFocusable()) { 4609 return nextFocus.itemView; 4610 } 4611 } 4612 final int limit = Math.min(itemCount, startFocusSearchIndex); 4613 for (int i = limit - 1; i >= 0; i--) { 4614 nextFocus = findViewHolderForAdapterPosition(i); 4615 if (nextFocus == null) { 4616 return null; 4617 } 4618 if (nextFocus.itemView.hasFocusable()) { 4619 return nextFocus.itemView; 4620 } 4621 } 4622 return null; 4623 } 4624 recoverFocusFromState()4625 private void recoverFocusFromState() { 4626 if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() 4627 || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS 4628 || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { 4629 // No-op if either of these cases happens: 4630 // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus 4631 // before its children and is focused (i.e. it already stole the focus away from its 4632 // descendants). 4633 return; 4634 } 4635 // only recover focus if RV itself has the focus or the focused view is hidden 4636 if (!isFocused()) { 4637 final View focusedChild = getFocusedChild(); 4638 if (!mChildHelper.isHidden(focusedChild)) { 4639 // If the currently focused child is hidden, apply the focus recovery logic. 4640 // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. 4641 return; 4642 } 4643 } 4644 ViewHolder focusTarget = null; 4645 // RV first attempts to locate the previously focused item to request focus on using 4646 // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to 4647 // find the next best candidate to request focus on based on mFocusedItemPosition. 4648 if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { 4649 focusTarget = findViewHolderForItemId(mState.mFocusedItemId); 4650 } 4651 View viewToFocus = null; 4652 if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) 4653 || !focusTarget.itemView.hasFocusable()) { 4654 if (mChildHelper.getChildCount() > 0) { 4655 // At this point, RV has focus and either of these conditions are true: 4656 // 1. There's no previously focused item either because RV received focused before 4657 // layout, or the previously focused item was removed, or RV doesn't have stable IDs 4658 // 2. Previous focus child is hidden, or 3. Previous focused child is no longer 4659 // focusable. In either of these cases, we make sure that RV still passes down the 4660 // focus to one of its focusable children using a best-effort algorithm. 4661 viewToFocus = findNextViewToFocus(); 4662 } 4663 } else { 4664 // looks like the focused item has been replaced with another view that represents the 4665 // same item in the adapter. Request focus on that. 4666 viewToFocus = focusTarget.itemView; 4667 } 4668 4669 if (viewToFocus != null) { 4670 if (mState.mFocusedSubChildId != NO_ID) { 4671 View child = viewToFocus.findViewById(mState.mFocusedSubChildId); 4672 if (child != null && child.isFocusable()) { 4673 viewToFocus = child; 4674 } 4675 } 4676 viewToFocus.requestFocus(); 4677 } 4678 } 4679 getDeepestFocusedViewWithId(View view)4680 private int getDeepestFocusedViewWithId(View view) { 4681 int lastKnownId = view.getId(); 4682 while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { 4683 view = ((ViewGroup) view).getFocusedChild(); 4684 final int id = view.getId(); 4685 if (id != View.NO_ID) { 4686 lastKnownId = view.getId(); 4687 } 4688 } 4689 return lastKnownId; 4690 } 4691 fillRemainingScrollValues(State state)4692 final void fillRemainingScrollValues(State state) { 4693 if (getScrollState() == SCROLL_STATE_SETTLING) { 4694 final OverScroller scroller = mViewFlinger.mOverScroller; 4695 state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX(); 4696 state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY(); 4697 } else { 4698 state.mRemainingScrollHorizontal = 0; 4699 state.mRemainingScrollVertical = 0; 4700 } 4701 } 4702 4703 /** 4704 * The first step of a layout where we; 4705 * - process adapter updates 4706 * - decide which animation should run 4707 * - save information about current views 4708 * - If necessary, run predictive layout and save its information 4709 */ dispatchLayoutStep1()4710 private void dispatchLayoutStep1() { 4711 mState.assertLayoutStep(State.STEP_START); 4712 fillRemainingScrollValues(mState); 4713 mState.mIsMeasuring = false; 4714 startInterceptRequestLayout(); 4715 mViewInfoStore.clear(); 4716 onEnterLayoutOrScroll(); 4717 processAdapterUpdatesAndSetAnimationFlags(); 4718 saveFocusInfo(); 4719 mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; 4720 mItemsAddedOrRemoved = mItemsChanged = false; 4721 mState.mInPreLayout = mState.mRunPredictiveAnimations; 4722 mState.mItemCount = mAdapter.getItemCount(); 4723 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 4724 4725 if (mState.mRunSimpleAnimations) { 4726 // Step 0: Find out where all non-removed items are, pre-layout 4727 int count = mChildHelper.getChildCount(); 4728 for (int i = 0; i < count; ++i) { 4729 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 4730 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { 4731 continue; 4732 } 4733 final ItemHolderInfo animationInfo = mItemAnimator 4734 .recordPreLayoutInformation(mState, holder, 4735 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), 4736 holder.getUnmodifiedPayloads()); 4737 mViewInfoStore.addToPreLayout(holder, animationInfo); 4738 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() 4739 && !holder.shouldIgnore() && !holder.isInvalid()) { 4740 long key = getChangedHolderKey(holder); 4741 // This is NOT the only place where a ViewHolder is added to old change holders 4742 // list. There is another case where: 4743 // * A VH is currently hidden but not deleted 4744 // * The hidden item is changed in the adapter 4745 // * Layout manager decides to layout the item in the pre-Layout pass (step1) 4746 // When this case is detected, RV will un-hide that view and add to the old 4747 // change holders list. 4748 mViewInfoStore.addToOldChangeHolders(key, holder); 4749 } 4750 } 4751 } 4752 if (mState.mRunPredictiveAnimations) { 4753 // Step 1: run prelayout: This will use the old positions of items. The layout manager 4754 // is expected to layout everything, even removed items (though not to add removed 4755 // items back to the container). This gives the pre-layout position of APPEARING views 4756 // which come into existence as part of the real layout. 4757 4758 // Save old positions so that LayoutManager can run its mapping logic. 4759 saveOldPositions(); 4760 final boolean didStructureChange = mState.mStructureChanged; 4761 mState.mStructureChanged = false; 4762 // temporarily disable flag because we are asking for previous layout 4763 mLayout.onLayoutChildren(mRecycler, mState); 4764 mState.mStructureChanged = didStructureChange; 4765 4766 for (int i = 0; i < mChildHelper.getChildCount(); ++i) { 4767 final View child = mChildHelper.getChildAt(i); 4768 final ViewHolder viewHolder = getChildViewHolderInt(child); 4769 if (viewHolder.shouldIgnore()) { 4770 continue; 4771 } 4772 if (!mViewInfoStore.isInPreLayout(viewHolder)) { 4773 int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); 4774 boolean wasHidden = viewHolder 4775 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 4776 if (!wasHidden) { 4777 flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 4778 } 4779 final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( 4780 mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); 4781 if (wasHidden) { 4782 recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); 4783 } else { 4784 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 4785 } 4786 } 4787 } 4788 // we don't process disappearing list because they may re-appear in post layout pass. 4789 clearOldPositions(); 4790 } else { 4791 clearOldPositions(); 4792 } 4793 onExitLayoutOrScroll(); 4794 stopInterceptRequestLayout(false); 4795 mState.mLayoutStep = State.STEP_LAYOUT; 4796 } 4797 4798 /** 4799 * The second layout step where we do the actual layout of the views for the final state. 4800 * This step might be run multiple times if necessary (e.g. measure). 4801 */ dispatchLayoutStep2()4802 private void dispatchLayoutStep2() { 4803 startInterceptRequestLayout(); 4804 onEnterLayoutOrScroll(); 4805 mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); 4806 mAdapterHelper.consumeUpdatesInOnePass(); 4807 mState.mItemCount = mAdapter.getItemCount(); 4808 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; 4809 if (mPendingSavedState != null && mAdapter.canRestoreState()) { 4810 if (mPendingSavedState.mLayoutState != null) { 4811 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); 4812 } 4813 mPendingSavedState = null; 4814 } 4815 // Step 2: Run layout 4816 mState.mInPreLayout = false; 4817 mLayout.onLayoutChildren(mRecycler, mState); 4818 4819 mState.mStructureChanged = false; 4820 4821 // onLayoutChildren may have caused client code to disable item animations; re-check 4822 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; 4823 mState.mLayoutStep = State.STEP_ANIMATIONS; 4824 onExitLayoutOrScroll(); 4825 stopInterceptRequestLayout(false); 4826 } 4827 4828 /** 4829 * The final step of the layout where we save the information about views for animations, 4830 * trigger animations and do any necessary cleanup. 4831 */ dispatchLayoutStep3()4832 private void dispatchLayoutStep3() { 4833 mState.assertLayoutStep(State.STEP_ANIMATIONS); 4834 startInterceptRequestLayout(); 4835 onEnterLayoutOrScroll(); 4836 mState.mLayoutStep = State.STEP_START; 4837 if (mState.mRunSimpleAnimations) { 4838 // Step 3: Find out where things are now, and process change animations. 4839 // traverse list in reverse because we may call animateChange in the loop which may 4840 // remove the target view holder. 4841 for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { 4842 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 4843 if (holder.shouldIgnore()) { 4844 continue; 4845 } 4846 long key = getChangedHolderKey(holder); 4847 final ItemHolderInfo animationInfo = mItemAnimator 4848 .recordPostLayoutInformation(mState, holder); 4849 ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); 4850 if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { 4851 // run a change animation 4852 4853 // If an Item is CHANGED but the updated version is disappearing, it creates 4854 // a conflicting case. 4855 // Since a view that is marked as disappearing is likely to be going out of 4856 // bounds, we run a change animation. Both views will be cleaned automatically 4857 // once their animations finish. 4858 // On the other hand, if it is the same view holder instance, we run a 4859 // disappearing animation instead because we are not going to rebind the updated 4860 // VH unless it is enforced by the layout manager. 4861 final boolean oldDisappearing = mViewInfoStore.isDisappearing( 4862 oldChangeViewHolder); 4863 final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); 4864 if (oldDisappearing && oldChangeViewHolder == holder) { 4865 // run disappear animation instead of change 4866 mViewInfoStore.addToPostLayout(holder, animationInfo); 4867 } else { 4868 final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( 4869 oldChangeViewHolder); 4870 // we add and remove so that any post info is merged. 4871 mViewInfoStore.addToPostLayout(holder, animationInfo); 4872 ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); 4873 if (preInfo == null) { 4874 handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); 4875 } else { 4876 animateChange(oldChangeViewHolder, holder, preInfo, postInfo, 4877 oldDisappearing, newDisappearing); 4878 } 4879 } 4880 } else { 4881 mViewInfoStore.addToPostLayout(holder, animationInfo); 4882 } 4883 } 4884 4885 // Step 4: Process view info lists and trigger animations 4886 mViewInfoStore.process(mViewInfoProcessCallback); 4887 } 4888 4889 mLayout.removeAndRecycleScrapInt(mRecycler); 4890 mState.mPreviousLayoutItemCount = mState.mItemCount; 4891 mDataSetHasChangedAfterLayout = false; 4892 mDispatchItemsChangedEvent = false; 4893 mState.mRunSimpleAnimations = false; 4894 4895 mState.mRunPredictiveAnimations = false; 4896 mLayout.mRequestedSimpleAnimations = false; 4897 if (mRecycler.mChangedScrap != null) { 4898 mRecycler.mChangedScrap.clear(); 4899 } 4900 if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { 4901 // Initial prefetch has expanded cache, so reset until next prefetch. 4902 // This prevents initial prefetches from expanding the cache permanently. 4903 mLayout.mPrefetchMaxCountObserved = 0; 4904 mLayout.mPrefetchMaxObservedInInitialPrefetch = false; 4905 mRecycler.updateViewCacheSize(); 4906 } 4907 4908 mLayout.onLayoutCompleted(mState); 4909 onExitLayoutOrScroll(); 4910 stopInterceptRequestLayout(false); 4911 mViewInfoStore.clear(); 4912 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { 4913 dispatchOnScrolled(0, 0); 4914 } 4915 recoverFocusFromState(); 4916 resetFocusInfo(); 4917 } 4918 4919 /** 4920 * This handles the case where there is an unexpected VH missing in the pre-layout map. 4921 * <p> 4922 * We might be able to detect the error in the application which will help the developer to 4923 * resolve the issue. 4924 * <p> 4925 * If it is not an expected error, we at least print an error to notify the developer and ignore 4926 * the animation. 4927 * 4928 * https://code.google.com/p/android/issues/detail?id=193958 4929 * 4930 * @param key The change key 4931 * @param holder Current ViewHolder 4932 * @param oldChangeViewHolder Changed ViewHolder 4933 */ handleMissingPreInfoForChangeError(long key, ViewHolder holder, ViewHolder oldChangeViewHolder)4934 private void handleMissingPreInfoForChangeError(long key, 4935 ViewHolder holder, ViewHolder oldChangeViewHolder) { 4936 // check if two VH have the same key, if so, print that as an error 4937 final int childCount = mChildHelper.getChildCount(); 4938 for (int i = 0; i < childCount; i++) { 4939 View view = mChildHelper.getChildAt(i); 4940 ViewHolder other = getChildViewHolderInt(view); 4941 if (other == holder) { 4942 continue; 4943 } 4944 final long otherKey = getChangedHolderKey(other); 4945 if (otherKey == key) { 4946 if (mAdapter != null && mAdapter.hasStableIds()) { 4947 throw new IllegalStateException("Two different ViewHolders have the same stable" 4948 + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" 4949 + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 4950 + exceptionLabel()); 4951 } else { 4952 throw new IllegalStateException("Two different ViewHolders have the same change" 4953 + " ID. This might happen due to inconsistent Adapter update events or" 4954 + " if the LayoutManager lays out the same View multiple times." 4955 + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder 4956 + exceptionLabel()); 4957 } 4958 } 4959 } 4960 // Very unlikely to happen but if it does, notify the developer. 4961 Log.e(TAG, "Problem while matching changed view holders with the new" 4962 + "ones. The pre-layout information for the change holder " + oldChangeViewHolder 4963 + " cannot be found but it is necessary for " + holder + exceptionLabel()); 4964 } 4965 4966 /** 4967 * Records the animation information for a view holder that was bounced from hidden list. It 4968 * also clears the bounce back flag. 4969 */ recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, ItemHolderInfo animationInfo)4970 void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, 4971 ItemHolderInfo animationInfo) { 4972 // looks like this view bounced back from hidden list! 4973 viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 4974 if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() 4975 && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { 4976 long key = getChangedHolderKey(viewHolder); 4977 mViewInfoStore.addToOldChangeHolders(key, viewHolder); 4978 } 4979 mViewInfoStore.addToPreLayout(viewHolder, animationInfo); 4980 } 4981 findMinMaxChildLayoutPositions(int[] into)4982 private void findMinMaxChildLayoutPositions(int[] into) { 4983 final int count = mChildHelper.getChildCount(); 4984 if (count == 0) { 4985 into[0] = NO_POSITION; 4986 into[1] = NO_POSITION; 4987 return; 4988 } 4989 int minPositionPreLayout = Integer.MAX_VALUE; 4990 int maxPositionPreLayout = Integer.MIN_VALUE; 4991 for (int i = 0; i < count; ++i) { 4992 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); 4993 if (holder.shouldIgnore()) { 4994 continue; 4995 } 4996 final int pos = holder.getLayoutPosition(); 4997 if (pos < minPositionPreLayout) { 4998 minPositionPreLayout = pos; 4999 } 5000 if (pos > maxPositionPreLayout) { 5001 maxPositionPreLayout = pos; 5002 } 5003 } 5004 into[0] = minPositionPreLayout; 5005 into[1] = maxPositionPreLayout; 5006 } 5007 didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout)5008 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { 5009 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); 5010 return mMinMaxLayoutPositions[0] != minPositionPreLayout 5011 || mMinMaxLayoutPositions[1] != maxPositionPreLayout; 5012 } 5013 5014 @Override removeDetachedView(View child, boolean animate)5015 protected void removeDetachedView(View child, boolean animate) { 5016 ViewHolder vh = getChildViewHolderInt(child); 5017 if (vh != null) { 5018 if (vh.isTmpDetached()) { 5019 vh.clearTmpDetachFlag(); 5020 } else if (!vh.shouldIgnore()) { 5021 throw new IllegalArgumentException("Called removeDetachedView with a view which" 5022 + " is not flagged as tmp detached." + vh + exceptionLabel()); 5023 } 5024 } else { 5025 if (sDebugAssertionsEnabled) { 5026 throw new IllegalArgumentException( 5027 "No ViewHolder found for child: " + child + exceptionLabel()); 5028 } 5029 } 5030 5031 // Clear any android.view.animation.Animation that may prevent the item from 5032 // detaching when being removed. If a child is re-added before the 5033 // lazy detach occurs, it will receive invalid attach/detach sequencing. 5034 child.clearAnimation(); 5035 5036 dispatchChildDetached(child); 5037 super.removeDetachedView(child, animate); 5038 } 5039 5040 /** 5041 * Returns a unique key to be used while handling change animations. 5042 * It might be child's position or stable id depending on the adapter type. 5043 */ getChangedHolderKey(ViewHolder holder)5044 long getChangedHolderKey(ViewHolder holder) { 5045 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; 5046 } 5047 animateAppearance(@onNull ViewHolder itemHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)5048 void animateAppearance(@NonNull ViewHolder itemHolder, 5049 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { 5050 itemHolder.setIsRecyclable(false); 5051 if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { 5052 postAnimationRunner(); 5053 } 5054 } 5055 animateDisappearance(@onNull ViewHolder holder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo)5056 void animateDisappearance(@NonNull ViewHolder holder, 5057 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { 5058 addAnimatingView(holder); 5059 holder.setIsRecyclable(false); 5060 if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { 5061 postAnimationRunner(); 5062 } 5063 } 5064 animateChange(@onNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, boolean oldHolderDisappearing, boolean newHolderDisappearing)5065 private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, 5066 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, 5067 boolean oldHolderDisappearing, boolean newHolderDisappearing) { 5068 oldHolder.setIsRecyclable(false); 5069 if (oldHolderDisappearing) { 5070 addAnimatingView(oldHolder); 5071 } 5072 if (oldHolder != newHolder) { 5073 if (newHolderDisappearing) { 5074 addAnimatingView(newHolder); 5075 } 5076 oldHolder.mShadowedHolder = newHolder; 5077 // old holder should disappear after animation ends 5078 addAnimatingView(oldHolder); 5079 mRecycler.unscrapView(oldHolder); 5080 newHolder.setIsRecyclable(false); 5081 newHolder.mShadowingHolder = oldHolder; 5082 } 5083 if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { 5084 postAnimationRunner(); 5085 } 5086 } 5087 5088 @Override onLayout(boolean changed, int l, int t, int r, int b)5089 protected void onLayout(boolean changed, int l, int t, int r, int b) { 5090 Trace.beginSection(TRACE_ON_LAYOUT_TAG); 5091 dispatchLayout(); 5092 Trace.endSection(); 5093 mFirstLayoutComplete = true; 5094 } 5095 5096 @Override requestLayout()5097 public void requestLayout() { 5098 if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) { 5099 super.requestLayout(); 5100 } else { 5101 mLayoutWasDefered = true; 5102 } 5103 } 5104 markItemDecorInsetsDirty()5105 void markItemDecorInsetsDirty() { 5106 final int childCount = mChildHelper.getUnfilteredChildCount(); 5107 for (int i = 0; i < childCount; i++) { 5108 final View child = mChildHelper.getUnfilteredChildAt(i); 5109 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 5110 } 5111 mRecycler.markItemDecorInsetsDirty(); 5112 } 5113 5114 @Override draw(@onNull Canvas c)5115 public void draw(@NonNull Canvas c) { 5116 super.draw(c); 5117 5118 final int count = mItemDecorations.size(); 5119 for (int i = 0; i < count; i++) { 5120 mItemDecorations.get(i).onDrawOver(c, this, mState); 5121 } 5122 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we 5123 // need find children closest to edges. Not sure if it is worth the effort. 5124 boolean needsInvalidate = false; 5125 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 5126 final int restore = c.save(); 5127 final int padding = mClipToPadding ? getPaddingBottom() : 0; 5128 c.rotate(270); 5129 c.translate(-getHeight() + padding, 0); 5130 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 5131 c.restoreToCount(restore); 5132 } 5133 if (mTopGlow != null && !mTopGlow.isFinished()) { 5134 final int restore = c.save(); 5135 if (mClipToPadding) { 5136 c.translate(getPaddingLeft(), getPaddingTop()); 5137 } 5138 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 5139 c.restoreToCount(restore); 5140 } 5141 if (mRightGlow != null && !mRightGlow.isFinished()) { 5142 final int restore = c.save(); 5143 final int width = getWidth(); 5144 final int padding = mClipToPadding ? getPaddingTop() : 0; 5145 c.rotate(90); 5146 c.translate(padding, -width); 5147 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 5148 c.restoreToCount(restore); 5149 } 5150 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 5151 final int restore = c.save(); 5152 c.rotate(180); 5153 if (mClipToPadding) { 5154 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); 5155 } else { 5156 c.translate(-getWidth(), -getHeight()); 5157 } 5158 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 5159 c.restoreToCount(restore); 5160 } 5161 5162 // If some views are animating, ItemDecorators are likely to move/change with them. 5163 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's 5164 // display lists are not invalidated. 5165 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 5166 && mItemAnimator.isRunning()) { 5167 needsInvalidate = true; 5168 } 5169 5170 if (needsInvalidate) { 5171 postInvalidateOnAnimation(); 5172 } 5173 } 5174 5175 @Override onDraw(@onNull Canvas c)5176 public void onDraw(@NonNull Canvas c) { 5177 super.onDraw(c); 5178 5179 final int count = mItemDecorations.size(); 5180 for (int i = 0; i < count; i++) { 5181 mItemDecorations.get(i).onDraw(c, this, mState); 5182 } 5183 } 5184 5185 @Override checkLayoutParams(ViewGroup.LayoutParams p)5186 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 5187 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 5188 } 5189 5190 @Override generateDefaultLayoutParams()5191 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 5192 if (mLayout == null) { 5193 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 5194 } 5195 return mLayout.generateDefaultLayoutParams(); 5196 } 5197 5198 @Override generateLayoutParams(AttributeSet attrs)5199 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 5200 if (mLayout == null) { 5201 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 5202 } 5203 return mLayout.generateLayoutParams(getContext(), attrs); 5204 } 5205 5206 @Override generateLayoutParams(ViewGroup.LayoutParams p)5207 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 5208 if (mLayout == null) { 5209 throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); 5210 } 5211 return mLayout.generateLayoutParams(p); 5212 } 5213 5214 /** 5215 * Returns true if RecyclerView is currently running some animations. 5216 * <p> 5217 * If you want to be notified when animations are finished, use 5218 * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}. 5219 * 5220 * @return True if there are some item animations currently running or waiting to be started. 5221 */ isAnimating()5222 public boolean isAnimating() { 5223 return mItemAnimator != null && mItemAnimator.isRunning(); 5224 } 5225 saveOldPositions()5226 void saveOldPositions() { 5227 final int childCount = mChildHelper.getUnfilteredChildCount(); 5228 for (int i = 0; i < childCount; i++) { 5229 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5230 if (sDebugAssertionsEnabled && holder.mPosition == -1 && !holder.isRemoved()) { 5231 throw new IllegalStateException("view holder cannot have position -1 unless it" 5232 + " is removed" + exceptionLabel()); 5233 } 5234 if (!holder.shouldIgnore()) { 5235 holder.saveOldPosition(); 5236 } 5237 } 5238 } 5239 clearOldPositions()5240 void clearOldPositions() { 5241 final int childCount = mChildHelper.getUnfilteredChildCount(); 5242 for (int i = 0; i < childCount; i++) { 5243 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5244 if (!holder.shouldIgnore()) { 5245 holder.clearOldPosition(); 5246 } 5247 } 5248 mRecycler.clearOldPositions(); 5249 } 5250 offsetPositionRecordsForMove(int from, int to)5251 void offsetPositionRecordsForMove(int from, int to) { 5252 final int childCount = mChildHelper.getUnfilteredChildCount(); 5253 final int start, end, inBetweenOffset; 5254 if (from < to) { 5255 start = from; 5256 end = to; 5257 inBetweenOffset = -1; 5258 } else { 5259 start = to; 5260 end = from; 5261 inBetweenOffset = 1; 5262 } 5263 5264 for (int i = 0; i < childCount; i++) { 5265 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5266 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 5267 continue; 5268 } 5269 if (sVerboseLoggingEnabled) { 5270 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " 5271 + holder); 5272 } 5273 if (holder.mPosition == from) { 5274 holder.offsetPosition(to - from, false); 5275 } else { 5276 holder.offsetPosition(inBetweenOffset, false); 5277 } 5278 5279 mState.mStructureChanged = true; 5280 } 5281 mRecycler.offsetPositionRecordsForMove(from, to); 5282 requestLayout(); 5283 } 5284 offsetPositionRecordsForInsert(int positionStart, int itemCount)5285 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 5286 final int childCount = mChildHelper.getUnfilteredChildCount(); 5287 for (int i = 0; i < childCount; i++) { 5288 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5289 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { 5290 if (sVerboseLoggingEnabled) { 5291 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " 5292 + holder + " now at position " + (holder.mPosition + itemCount)); 5293 } 5294 holder.offsetPosition(itemCount, false); 5295 mState.mStructureChanged = true; 5296 } 5297 } 5298 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 5299 requestLayout(); 5300 } 5301 offsetPositionRecordsForRemove(int positionStart, int itemCount, boolean applyToPreLayout)5302 void offsetPositionRecordsForRemove(int positionStart, int itemCount, 5303 boolean applyToPreLayout) { 5304 final int positionEnd = positionStart + itemCount; 5305 final int childCount = mChildHelper.getUnfilteredChildCount(); 5306 for (int i = 0; i < childCount; i++) { 5307 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5308 if (holder != null && !holder.shouldIgnore()) { 5309 if (holder.mPosition >= positionEnd) { 5310 if (sVerboseLoggingEnabled) { 5311 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 5312 + " holder " + holder + " now at position " 5313 + (holder.mPosition - itemCount)); 5314 } 5315 holder.offsetPosition(-itemCount, applyToPreLayout); 5316 mState.mStructureChanged = true; 5317 } else if (holder.mPosition >= positionStart) { 5318 if (sVerboseLoggingEnabled) { 5319 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i 5320 + " holder " + holder + " now REMOVED"); 5321 } 5322 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, 5323 applyToPreLayout); 5324 mState.mStructureChanged = true; 5325 } 5326 } 5327 } 5328 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); 5329 requestLayout(); 5330 } 5331 5332 /** 5333 * Rebind existing views for the given range, or create as needed. 5334 * 5335 * @param positionStart Adapter position to start at 5336 * @param itemCount Number of views that must explicitly be rebound 5337 */ viewRangeUpdate(int positionStart, int itemCount, Object payload)5338 void viewRangeUpdate(int positionStart, int itemCount, Object payload) { 5339 final int childCount = mChildHelper.getUnfilteredChildCount(); 5340 final int positionEnd = positionStart + itemCount; 5341 5342 for (int i = 0; i < childCount; i++) { 5343 final View child = mChildHelper.getUnfilteredChildAt(i); 5344 final ViewHolder holder = getChildViewHolderInt(child); 5345 if (holder == null || holder.shouldIgnore()) { 5346 continue; 5347 } 5348 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 5349 // We re-bind these view holders after pre-processing is complete so that 5350 // ViewHolders have their final positions assigned. 5351 holder.addFlags(ViewHolder.FLAG_UPDATE); 5352 holder.addChangePayload(payload); 5353 // lp cannot be null since we get ViewHolder from it. 5354 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 5355 } 5356 } 5357 mRecycler.viewRangeUpdate(positionStart, itemCount); 5358 } 5359 canReuseUpdatedViewHolder(ViewHolder viewHolder)5360 boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { 5361 return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, 5362 viewHolder.getUnmodifiedPayloads()); 5363 } 5364 5365 /** 5366 * Processes the fact that, as far as we can tell, the data set has completely changed. 5367 * 5368 * <ul> 5369 * <li>Once layout occurs, all attached items should be discarded or animated. 5370 * <li>Attached items are labeled as invalid. 5371 * <li>Because items may still be prefetched between a "data set completely changed" 5372 * event and a layout event, all cached items are discarded. 5373 * </ul> 5374 * 5375 * @param dispatchItemsChanged Whether to call 5376 * {@link LayoutManager#onItemsChanged(RecyclerView)} during 5377 * measure/layout. 5378 */ processDataSetCompletelyChanged(boolean dispatchItemsChanged)5379 void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { 5380 mDispatchItemsChangedEvent |= dispatchItemsChanged; 5381 mDataSetHasChangedAfterLayout = true; 5382 markKnownViewsInvalid(); 5383 } 5384 5385 /** 5386 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 5387 * data change event. 5388 */ markKnownViewsInvalid()5389 void markKnownViewsInvalid() { 5390 final int childCount = mChildHelper.getUnfilteredChildCount(); 5391 for (int i = 0; i < childCount; i++) { 5392 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5393 if (holder != null && !holder.shouldIgnore()) { 5394 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 5395 } 5396 } 5397 markItemDecorInsetsDirty(); 5398 mRecycler.markKnownViewsInvalid(); 5399 } 5400 5401 /** 5402 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method 5403 * will trigger a {@link #requestLayout()} call. 5404 */ invalidateItemDecorations()5405 public void invalidateItemDecorations() { 5406 if (mItemDecorations.size() == 0) { 5407 return; 5408 } 5409 if (mLayout != null) { 5410 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" 5411 + " or layout"); 5412 } 5413 markItemDecorInsetsDirty(); 5414 requestLayout(); 5415 } 5416 5417 /** 5418 * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's 5419 * focus even if the View representing the Item is replaced during a layout calculation. 5420 * <p> 5421 * By default, this value is {@code true}. 5422 * 5423 * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses 5424 * focus. 5425 * @see #setPreserveFocusAfterLayout(boolean) 5426 */ getPreserveFocusAfterLayout()5427 public boolean getPreserveFocusAfterLayout() { 5428 return mPreserveFocusAfterLayout; 5429 } 5430 5431 /** 5432 * Set whether the RecyclerView should try to keep the same Item focused after a layout 5433 * calculation or not. 5434 * <p> 5435 * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, 5436 * views may lose focus during a layout calculation as their state changes or they are replaced 5437 * with another view due to type change or animation. In these cases, RecyclerView can request 5438 * focus on the new view automatically. 5439 * 5440 * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a 5441 * layout calculations. Defaults to true. 5442 * @see #getPreserveFocusAfterLayout() 5443 */ setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout)5444 public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { 5445 mPreserveFocusAfterLayout = preserveFocusAfterLayout; 5446 } 5447 5448 /** 5449 * Retrieve the {@link ViewHolder} for the given child view. 5450 * 5451 * @param child Child of this RecyclerView to query for its ViewHolder 5452 * @return The child view's ViewHolder 5453 */ getChildViewHolder(@onNull View child)5454 public ViewHolder getChildViewHolder(@NonNull View child) { 5455 final ViewParent parent = child.getParent(); 5456 if (parent != null && parent != this) { 5457 throw new IllegalArgumentException("View " + child + " is not a direct child of " 5458 + this); 5459 } 5460 return getChildViewHolderInt(child); 5461 } 5462 5463 /** 5464 * Traverses the ancestors of the given view and returns the item view that contains it and 5465 * also a direct child of the RecyclerView. This returned view can be used to get the 5466 * ViewHolder by calling {@link #getChildViewHolder(View)}. 5467 * 5468 * @param view The view that is a descendant of the RecyclerView. 5469 * @return The direct child of the RecyclerView which contains the given view or null if the 5470 * provided view is not a descendant of this RecyclerView. 5471 * @see #getChildViewHolder(View) 5472 * @see #findContainingViewHolder(View) 5473 */ findContainingItemView(@onNull View view)5474 public @Nullable View findContainingItemView(@NonNull View view) { 5475 ViewParent parent = view.getParent(); 5476 while (parent != null && parent != this && parent instanceof View) { 5477 view = (View) parent; 5478 parent = view.getParent(); 5479 } 5480 return parent == this ? view : null; 5481 } 5482 5483 /** 5484 * Returns the ViewHolder that contains the given view. 5485 * 5486 * @param view The view that is a descendant of the RecyclerView. 5487 * @return The ViewHolder that contains the given view or null if the provided view is not a 5488 * descendant of this RecyclerView. 5489 */ findContainingViewHolder(@onNull View view)5490 public @Nullable ViewHolder findContainingViewHolder(@NonNull View view) { 5491 View itemView = findContainingItemView(view); 5492 return itemView == null ? null : getChildViewHolder(itemView); 5493 } 5494 5495 getChildViewHolderInt(View child)5496 static ViewHolder getChildViewHolderInt(View child) { 5497 if (child == null) { 5498 return null; 5499 } 5500 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 5501 } 5502 5503 /** 5504 * @deprecated use {@link #getChildAdapterPosition(View)} or 5505 * {@link #getChildLayoutPosition(View)}. 5506 */ 5507 @Deprecated getChildPosition(@onNull View child)5508 public int getChildPosition(@NonNull View child) { 5509 return getChildAdapterPosition(child); 5510 } 5511 5512 /** 5513 * Return the adapter position that the given child view corresponds to. 5514 * 5515 * @param child Child View to query 5516 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 5517 */ getChildAdapterPosition(@onNull View child)5518 public int getChildAdapterPosition(@NonNull View child) { 5519 final ViewHolder holder = getChildViewHolderInt(child); 5520 return holder != null ? holder.getAbsoluteAdapterPosition() : NO_POSITION; 5521 } 5522 5523 /** 5524 * Return the adapter position of the given child view as of the latest completed layout pass. 5525 * <p> 5526 * This position may not be equal to Item's adapter position if there are pending changes 5527 * in the adapter which have not been reflected to the layout yet. 5528 * 5529 * @param child Child View to query 5530 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if 5531 * the View is representing a removed item. 5532 */ getChildLayoutPosition(@onNull View child)5533 public int getChildLayoutPosition(@NonNull View child) { 5534 final ViewHolder holder = getChildViewHolderInt(child); 5535 return holder != null ? holder.getLayoutPosition() : NO_POSITION; 5536 } 5537 5538 /** 5539 * Return the stable item id that the given child view corresponds to. 5540 * 5541 * @param child Child View to query 5542 * @return Item id corresponding to the given view or {@link #NO_ID} 5543 */ getChildItemId(@onNull View child)5544 public long getChildItemId(@NonNull View child) { 5545 if (mAdapter == null || !mAdapter.hasStableIds()) { 5546 return NO_ID; 5547 } 5548 final ViewHolder holder = getChildViewHolderInt(child); 5549 return holder != null ? holder.getItemId() : NO_ID; 5550 } 5551 5552 /** 5553 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or 5554 * {@link #findViewHolderForAdapterPosition(int)} 5555 */ 5556 @Deprecated findViewHolderForPosition(int position)5557 public @Nullable ViewHolder findViewHolderForPosition(int position) { 5558 return findViewHolderForPosition(position, false); 5559 } 5560 5561 /** 5562 * Return the ViewHolder for the item in the given position of the data set as of the latest 5563 * layout pass. 5564 * <p> 5565 * This method checks only the children of RecyclerView. If the item at the given 5566 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 5567 * <p> 5568 * Note that when Adapter contents change, ViewHolder positions are not updated until the 5569 * next layout calculation. If there are pending adapter updates, the return value of this 5570 * method may not match your adapter contents. You can use 5571 * #{@link ViewHolder#getBindingAdapterPosition()} to get the current adapter position 5572 * of a ViewHolder. If the {@link Adapter} that is assigned to the RecyclerView is an adapter 5573 * that combines other adapters (e.g. {@link ConcatAdapter}), you can use the 5574 * {@link ViewHolder#getBindingAdapter()}) to find the position relative to the {@link Adapter} 5575 * that bound the {@link ViewHolder}. 5576 * <p> 5577 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 5578 * with the same layout position representing the same Item. In this case, the updated 5579 * ViewHolder will be returned. 5580 * 5581 * @param position The position of the item in the data set of the adapter 5582 * @return The ViewHolder at <code>position</code> or null if there is no such item 5583 */ findViewHolderForLayoutPosition(int position)5584 public @Nullable ViewHolder findViewHolderForLayoutPosition(int position) { 5585 return findViewHolderForPosition(position, false); 5586 } 5587 5588 /** 5589 * Return the ViewHolder for the item in the given position of the data set. Unlike 5590 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending 5591 * adapter changes that may not be reflected to the layout yet. On the other hand, if 5592 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been 5593 * calculated yet, this method will return <code>null</code> since the new positions of views 5594 * are unknown until the layout is calculated. 5595 * <p> 5596 * This method checks only the children of RecyclerView. If the item at the given 5597 * <code>position</code> is not laid out, it <em>will not</em> create a new one. 5598 * <p> 5599 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders 5600 * representing the same Item. In this case, the updated ViewHolder will be returned. 5601 * 5602 * @param position The position of the item in the data set of the adapter 5603 * @return The ViewHolder at <code>position</code> or null if there is no such item 5604 */ findViewHolderForAdapterPosition(int position)5605 public @Nullable ViewHolder findViewHolderForAdapterPosition(int position) { 5606 if (mDataSetHasChangedAfterLayout) { 5607 return null; 5608 } 5609 final int childCount = mChildHelper.getUnfilteredChildCount(); 5610 // hidden VHs are not preferred but if that is the only one we find, we rather return it 5611 ViewHolder hidden = null; 5612 for (int i = 0; i < childCount; i++) { 5613 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5614 if (holder != null && !holder.isRemoved() 5615 && getAdapterPositionInRecyclerView(holder) == position) { 5616 if (mChildHelper.isHidden(holder.itemView)) { 5617 hidden = holder; 5618 } else { 5619 return holder; 5620 } 5621 } 5622 } 5623 return hidden; 5624 } 5625 findViewHolderForPosition(int position, boolean checkNewPosition)5626 @Nullable ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { 5627 final int childCount = mChildHelper.getUnfilteredChildCount(); 5628 ViewHolder hidden = null; 5629 for (int i = 0; i < childCount; i++) { 5630 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5631 if (holder != null && !holder.isRemoved()) { 5632 if (checkNewPosition) { 5633 if (holder.mPosition != position) { 5634 continue; 5635 } 5636 } else if (holder.getLayoutPosition() != position) { 5637 continue; 5638 } 5639 if (mChildHelper.isHidden(holder.itemView)) { 5640 hidden = holder; 5641 } else { 5642 return holder; 5643 } 5644 } 5645 } 5646 // This method should not query cached views. It creates a problem during adapter updates 5647 // when we are dealing with already laid out views. Also, for the public method, it is more 5648 // reasonable to return null if position is not laid out. 5649 return hidden; 5650 } 5651 5652 /** 5653 * Return the ViewHolder for the item with the given id. The RecyclerView must 5654 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 5655 * return a non-null value. 5656 * <p> 5657 * This method checks only the children of RecyclerView. If the item with the given 5658 * <code>id</code> is not laid out, it <em>will not</em> create a new one. 5659 * 5660 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the 5661 * same id. In this case, the updated ViewHolder will be returned. 5662 * 5663 * @param id The id for the requested item 5664 * @return The ViewHolder with the given <code>id</code> or null if there is no such item 5665 */ findViewHolderForItemId(long id)5666 public ViewHolder findViewHolderForItemId(long id) { 5667 if (mAdapter == null || !mAdapter.hasStableIds()) { 5668 return null; 5669 } 5670 final int childCount = mChildHelper.getUnfilteredChildCount(); 5671 ViewHolder hidden = null; 5672 for (int i = 0; i < childCount; i++) { 5673 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); 5674 if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { 5675 if (mChildHelper.isHidden(holder.itemView)) { 5676 hidden = holder; 5677 } else { 5678 return holder; 5679 } 5680 } 5681 } 5682 return hidden; 5683 } 5684 5685 /** 5686 * Find the topmost view under the given point. 5687 * 5688 * @param x Horizontal position in pixels to search 5689 * @param y Vertical position in pixels to search 5690 * @return The child view under (x, y) or null if no matching child is found 5691 */ findChildViewUnder(float x, float y)5692 public @Nullable View findChildViewUnder(float x, float y) { 5693 final int count = mChildHelper.getChildCount(); 5694 for (int i = count - 1; i >= 0; i--) { 5695 final View child = mChildHelper.getChildAt(i); 5696 final float translationX = child.getTranslationX(); 5697 final float translationY = child.getTranslationY(); 5698 if (x >= child.getLeft() + translationX 5699 && x <= child.getRight() + translationX 5700 && y >= child.getTop() + translationY 5701 && y <= child.getBottom() + translationY) { 5702 return child; 5703 } 5704 } 5705 return null; 5706 } 5707 5708 @Override drawChild(@onNull Canvas canvas, View child, long drawingTime)5709 public boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) { 5710 return super.drawChild(canvas, child, drawingTime); 5711 } 5712 5713 /** 5714 * Offset the bounds of all child views by <code>dy</code> pixels. 5715 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 5716 * 5717 * @param dy Vertical pixel offset to apply to the bounds of all child views 5718 */ offsetChildrenVertical(@x int dy)5719 public void offsetChildrenVertical(@Px int dy) { 5720 final int childCount = mChildHelper.getChildCount(); 5721 for (int i = 0; i < childCount; i++) { 5722 mChildHelper.getChildAt(i).offsetTopAndBottom(dy); 5723 } 5724 } 5725 5726 /** 5727 * Called when an item view is attached to this RecyclerView. 5728 * 5729 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 5730 * of child views as they become attached. This will be called before a 5731 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 5732 * changes.</p> 5733 * 5734 * @param child Child view that is now attached to this RecyclerView and its associated window 5735 */ onChildAttachedToWindow(@onNull View child)5736 public void onChildAttachedToWindow(@NonNull View child) { 5737 } 5738 5739 /** 5740 * Called when an item view is detached from this RecyclerView. 5741 * 5742 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 5743 * of child views as they become detached. This will be called as a 5744 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 5745 * 5746 * @param child Child view that is now detached from this RecyclerView and its associated window 5747 */ onChildDetachedFromWindow(@onNull View child)5748 public void onChildDetachedFromWindow(@NonNull View child) { 5749 } 5750 5751 /** 5752 * Offset the bounds of all child views by <code>dx</code> pixels. 5753 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 5754 * 5755 * @param dx Horizontal pixel offset to apply to the bounds of all child views 5756 */ offsetChildrenHorizontal(@x int dx)5757 public void offsetChildrenHorizontal(@Px int dx) { 5758 final int childCount = mChildHelper.getChildCount(); 5759 for (int i = 0; i < childCount; i++) { 5760 mChildHelper.getChildAt(i).offsetLeftAndRight(dx); 5761 } 5762 } 5763 5764 /** 5765 * Returns the bounds of the view including its decoration and margins. 5766 * 5767 * @param view The view element to check 5768 * @param outBounds A rect that will receive the bounds of the element including its 5769 * decoration and margins. 5770 */ getDecoratedBoundsWithMargins(@onNull View view, @NonNull Rect outBounds)5771 public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { 5772 getDecoratedBoundsWithMarginsInt(view, outBounds); 5773 } 5774 getDecoratedBoundsWithMarginsInt(View view, Rect outBounds)5775 static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { 5776 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 5777 final Rect insets = lp.mDecorInsets; 5778 outBounds.set(view.getLeft() - insets.left - lp.leftMargin, 5779 view.getTop() - insets.top - lp.topMargin, 5780 view.getRight() + insets.right + lp.rightMargin, 5781 view.getBottom() + insets.bottom + lp.bottomMargin); 5782 } 5783 getItemDecorInsetsForChild(View child)5784 Rect getItemDecorInsetsForChild(View child) { 5785 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 5786 if (!lp.mInsetsDirty) { 5787 return lp.mDecorInsets; 5788 } 5789 5790 if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { 5791 // changed/invalid items should not be updated until they are rebound. 5792 return lp.mDecorInsets; 5793 } 5794 final Rect insets = lp.mDecorInsets; 5795 insets.set(0, 0, 0, 0); 5796 final int decorCount = mItemDecorations.size(); 5797 for (int i = 0; i < decorCount; i++) { 5798 mTempRect.set(0, 0, 0, 0); 5799 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 5800 insets.left += mTempRect.left; 5801 insets.top += mTempRect.top; 5802 insets.right += mTempRect.right; 5803 insets.bottom += mTempRect.bottom; 5804 } 5805 lp.mInsetsDirty = false; 5806 return insets; 5807 } 5808 5809 /** 5810 * Called when the scroll position of this RecyclerView changes. Subclasses should use 5811 * this method to respond to scrolling within the adapter's data set instead of an explicit 5812 * listener. 5813 * 5814 * <p>This method will always be invoked before listeners. If a subclass needs to perform 5815 * any additional upkeep or bookkeeping after scrolling but before listeners run, 5816 * this is a good place to do so.</p> 5817 * 5818 * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives 5819 * the distance scrolled in either direction within the adapter's data set instead of absolute 5820 * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from 5821 * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive 5822 * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which 5823 * do not correspond to the data set scroll position. However, some subclasses may choose 5824 * to use these fields as special offsets.</p> 5825 * 5826 * @param dx horizontal distance scrolled in pixels 5827 * @param dy vertical distance scrolled in pixels 5828 */ onScrolled(@x int dx, @Px int dy)5829 public void onScrolled(@Px int dx, @Px int dy) { 5830 // Do nothing 5831 } 5832 dispatchOnScrolled(int hresult, int vresult)5833 void dispatchOnScrolled(int hresult, int vresult) { 5834 mDispatchScrollCounter++; 5835 // Pass the current scrollX/scrollY values as current values. No actual change in these 5836 // properties occurred. Pass negative hresult and vresult as old values so that 5837 // postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt) in onScrollChanged 5838 // sends the scrolled accessibility event correctly. 5839 final int scrollX = getScrollX(); 5840 final int scrollY = getScrollY(); 5841 onScrollChanged(scrollX, scrollY, scrollX - hresult, scrollY - vresult); 5842 5843 // Pass the real deltas to onScrolled, the RecyclerView-specific method. 5844 onScrolled(hresult, vresult); 5845 5846 // Invoke listeners last. Subclassed view methods always handle the event first. 5847 // All internal state is consistent by the time listeners are invoked. 5848 if (mScrollListener != null) { 5849 mScrollListener.onScrolled(this, hresult, vresult); 5850 } 5851 if (mScrollListeners != null) { 5852 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 5853 mScrollListeners.get(i).onScrolled(this, hresult, vresult); 5854 } 5855 } 5856 mDispatchScrollCounter--; 5857 } 5858 5859 /** 5860 * Called when the scroll state of this RecyclerView changes. Subclasses should use this 5861 * method to respond to state changes instead of an explicit listener. 5862 * 5863 * <p>This method will always be invoked before listeners, but after the LayoutManager 5864 * responds to the scroll state change.</p> 5865 * 5866 * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, 5867 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} 5868 */ onScrollStateChanged(int state)5869 public void onScrollStateChanged(int state) { 5870 // Do nothing 5871 } 5872 5873 /** 5874 * Copied from OverScroller, this returns the distance that a fling with the given velocity 5875 * will go. 5876 * @param velocity The velocity of the fling 5877 * @return The distance that will be traveled by a fling of the given velocity. 5878 */ getSplineFlingDistance(int velocity)5879 private float getSplineFlingDistance(int velocity) { 5880 final double l = 5881 Math.log(INFLEXION * Math.abs(velocity) / (SCROLL_FRICTION * mPhysicalCoef)); 5882 final double decelMinusOne = DECELERATION_RATE - 1.0; 5883 return (float) (SCROLL_FRICTION * mPhysicalCoef 5884 * Math.exp(DECELERATION_RATE / decelMinusOne * l)); 5885 } 5886 dispatchOnScrollStateChanged(int state)5887 void dispatchOnScrollStateChanged(int state) { 5888 // Let the LayoutManager go first; this allows it to bring any properties into 5889 // a consistent state before the RecyclerView subclass responds. 5890 if (mLayout != null) { 5891 mLayout.onScrollStateChanged(state); 5892 } 5893 5894 // Let the RecyclerView subclass handle this event next; any LayoutManager property 5895 // changes will be reflected by this time. 5896 onScrollStateChanged(state); 5897 5898 // Listeners go last. All other internal state is consistent by this point. 5899 if (mScrollListener != null) { 5900 mScrollListener.onScrollStateChanged(this, state); 5901 } 5902 if (mScrollListeners != null) { 5903 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 5904 mScrollListeners.get(i).onScrollStateChanged(this, state); 5905 } 5906 } 5907 } 5908 5909 /** 5910 * Returns whether there are pending adapter updates which are not yet applied to the layout. 5911 * <p> 5912 * If this method returns <code>true</code>, it means that what user is currently seeing may not 5913 * reflect them adapter contents (depending on what has changed). 5914 * You may use this information to defer or cancel some operations. 5915 * <p> 5916 * This method returns true if RecyclerView has not yet calculated the first layout after it is 5917 * attached to the Window or the Adapter has been replaced. 5918 * 5919 * @return True if there are some adapter updates which are not yet reflected to layout or false 5920 * if layout is up to date. 5921 */ hasPendingAdapterUpdates()5922 public boolean hasPendingAdapterUpdates() { 5923 return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout 5924 || mAdapterHelper.hasPendingUpdates(); 5925 } 5926 5927 // Effectively private. Set to default to avoid synthetic accessor. 5928 class ViewFlinger implements Runnable { 5929 private int mLastFlingX; 5930 private int mLastFlingY; 5931 OverScroller mOverScroller; 5932 Interpolator mInterpolator = sQuinticInterpolator; 5933 5934 // When set to true, postOnAnimation callbacks are delayed until the run method completes 5935 private boolean mEatRunOnAnimationRequest = false; 5936 5937 // Tracks if postAnimationCallback should be re-attached when it is done 5938 private boolean mReSchedulePostAnimationCallback = false; 5939 ViewFlinger()5940 ViewFlinger() { 5941 mOverScroller = new OverScroller(getContext(), sQuinticInterpolator); 5942 } 5943 5944 @Override run()5945 public void run() { 5946 if (mLayout == null) { 5947 stop(); 5948 return; // no layout, cannot scroll. 5949 } 5950 5951 mReSchedulePostAnimationCallback = false; 5952 mEatRunOnAnimationRequest = true; 5953 5954 consumePendingUpdateOperations(); 5955 5956 // TODO(72745539): After reviewing the code, it seems to me we may actually want to 5957 // update the reference to the OverScroller after onAnimation. It looks to me like 5958 // it is possible that a new OverScroller could be created (due to a new Interpolator 5959 // being used), when the current OverScroller knows it's done after 5960 // scroller.computeScrollOffset() is called. If that happens, and we don't update the 5961 // reference, it seems to me that we could prematurely stop the newly created scroller 5962 // due to setScrollState(SCROLL_STATE_IDLE) being called below. 5963 5964 // Keep a local reference so that if it is changed during onAnimation method, it won't 5965 // cause unexpected behaviors 5966 final OverScroller scroller = mOverScroller; 5967 if (scroller.computeScrollOffset()) { 5968 final int x = scroller.getCurrX(); 5969 final int y = scroller.getCurrY(); 5970 int unconsumedX = x - mLastFlingX; 5971 int unconsumedY = y - mLastFlingY; 5972 mLastFlingX = x; 5973 mLastFlingY = y; 5974 5975 unconsumedX = consumeFlingInHorizontalStretch(unconsumedX); 5976 unconsumedY = consumeFlingInVerticalStretch(unconsumedY); 5977 5978 int consumedX = 0; 5979 int consumedY = 0; 5980 5981 // Nested Pre Scroll 5982 mReusableIntPair[0] = 0; 5983 mReusableIntPair[1] = 0; 5984 if (dispatchNestedPreScroll(unconsumedX, unconsumedY, mReusableIntPair, null, 5985 TYPE_NON_TOUCH)) { 5986 unconsumedX -= mReusableIntPair[0]; 5987 unconsumedY -= mReusableIntPair[1]; 5988 } 5989 5990 // Based on movement, we may want to trigger the hiding of existing over scroll 5991 // glows. 5992 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 5993 considerReleasingGlowsOnScroll(unconsumedX, unconsumedY); 5994 } 5995 5996 // Local Scroll 5997 if (mAdapter != null) { 5998 mReusableIntPair[0] = 0; 5999 mReusableIntPair[1] = 0; 6000 scrollStep(unconsumedX, unconsumedY, mReusableIntPair); 6001 consumedX = mReusableIntPair[0]; 6002 consumedY = mReusableIntPair[1]; 6003 unconsumedX -= consumedX; 6004 unconsumedY -= consumedY; 6005 6006 // If SmoothScroller exists, this ViewFlinger was started by it, so we must 6007 // report back to SmoothScroller. 6008 SmoothScroller smoothScroller = mLayout.mSmoothScroller; 6009 if (smoothScroller != null && !smoothScroller.isPendingInitialRun() 6010 && smoothScroller.isRunning()) { 6011 final int adapterSize = mState.getItemCount(); 6012 if (adapterSize == 0) { 6013 smoothScroller.stop(); 6014 } else if (smoothScroller.getTargetPosition() >= adapterSize) { 6015 smoothScroller.setTargetPosition(adapterSize - 1); 6016 smoothScroller.onAnimation(consumedX, consumedY); 6017 } else { 6018 smoothScroller.onAnimation(consumedX, consumedY); 6019 } 6020 } 6021 } 6022 6023 if (!mItemDecorations.isEmpty()) { 6024 invalidate(); 6025 } 6026 6027 // Nested Post Scroll 6028 mReusableIntPair[0] = 0; 6029 mReusableIntPair[1] = 0; 6030 dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, null, 6031 TYPE_NON_TOUCH, mReusableIntPair); 6032 unconsumedX -= mReusableIntPair[0]; 6033 unconsumedY -= mReusableIntPair[1]; 6034 6035 if (consumedX != 0 || consumedY != 0) { 6036 dispatchOnScrolled(consumedX, consumedY); 6037 } 6038 6039 if (!awakenScrollBars()) { 6040 invalidate(); 6041 } 6042 6043 // We are done scrolling if scroller is finished, or for both the x and y dimension, 6044 // we are done scrolling or we can't scroll further (we know we can't scroll further 6045 // when we have unconsumed scroll distance). It's possible that we don't need 6046 // to also check for scroller.isFinished() at all, but no harm in doing so in case 6047 // of old bugs in Overscroller. 6048 boolean scrollerFinishedX = scroller.getCurrX() == scroller.getFinalX(); 6049 boolean scrollerFinishedY = scroller.getCurrY() == scroller.getFinalY(); 6050 final boolean doneScrolling = scroller.isFinished() 6051 || ((scrollerFinishedX || unconsumedX != 0) 6052 && (scrollerFinishedY || unconsumedY != 0)); 6053 6054 // Get the current smoothScroller. It may have changed by this point and we need to 6055 // make sure we don't stop scrolling if it has changed and it's pending an initial 6056 // run. 6057 SmoothScroller smoothScroller = mLayout.mSmoothScroller; 6058 boolean smoothScrollerPending = 6059 smoothScroller != null && smoothScroller.isPendingInitialRun(); 6060 6061 if (!smoothScrollerPending && doneScrolling) { 6062 // If we are done scrolling and the layout's SmoothScroller is not pending, 6063 // do the things we do at the end of a scroll and don't postOnAnimation. 6064 6065 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { 6066 final int vel = (int) scroller.getCurrVelocity(); 6067 int velX = unconsumedX < 0 ? -vel : unconsumedX > 0 ? vel : 0; 6068 int velY = unconsumedY < 0 ? -vel : unconsumedY > 0 ? vel : 0; 6069 absorbGlows(velX, velY); 6070 } 6071 6072 if (ALLOW_THREAD_GAP_WORK) { 6073 mPrefetchRegistry.clearPrefetchPositions(); 6074 } 6075 } else { 6076 // Otherwise continue the scroll. 6077 6078 postOnAnimation(); 6079 if (mGapWorker != null) { 6080 mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY); 6081 } 6082 } 6083 if (Build.VERSION.SDK_INT >= 35) { 6084 Api35Impl.setFrameContentVelocity(RecyclerView.this, 6085 Math.abs(scroller.getCurrVelocity())); 6086 } 6087 } 6088 6089 SmoothScroller smoothScroller = mLayout.mSmoothScroller; 6090 // call this after the onAnimation is complete not to have inconsistent callbacks etc. 6091 if (smoothScroller != null && smoothScroller.isPendingInitialRun()) { 6092 smoothScroller.onAnimation(0, 0); 6093 } 6094 6095 mEatRunOnAnimationRequest = false; 6096 if (mReSchedulePostAnimationCallback) { 6097 internalPostOnAnimation(); 6098 } else { 6099 setScrollState(SCROLL_STATE_IDLE); 6100 stopNestedScroll(TYPE_NON_TOUCH); 6101 } 6102 } 6103 postOnAnimation()6104 void postOnAnimation() { 6105 if (mEatRunOnAnimationRequest) { 6106 mReSchedulePostAnimationCallback = true; 6107 } else { 6108 internalPostOnAnimation(); 6109 } 6110 } 6111 internalPostOnAnimation()6112 private void internalPostOnAnimation() { 6113 removeCallbacks(this); 6114 ViewCompat.postOnAnimation(RecyclerView.this, this); 6115 } 6116 fling(int velocityX, int velocityY)6117 public void fling(int velocityX, int velocityY) { 6118 setScrollState(SCROLL_STATE_SETTLING); 6119 mLastFlingX = mLastFlingY = 0; 6120 // Because you can't define a custom interpolator for flinging, we should make sure we 6121 // reset ourselves back to the teh default interpolator in case a different call 6122 // changed our interpolator. 6123 if (mInterpolator != sQuinticInterpolator) { 6124 mInterpolator = sQuinticInterpolator; 6125 mOverScroller = new OverScroller(getContext(), sQuinticInterpolator); 6126 } 6127 mOverScroller.fling(0, 0, velocityX, velocityY, 6128 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 6129 postOnAnimation(); 6130 } 6131 6132 /** 6133 * Smooth scrolls the RecyclerView by a given distance. 6134 * 6135 * @param dx x distance in pixels. 6136 * @param dy y distance in pixels. 6137 * @param duration Duration of the animation in milliseconds. Set to 6138 * {@link #UNDEFINED_DURATION} to have the duration automatically 6139 * calculated 6140 * based on an internally defined standard velocity. 6141 * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null}, 6142 * RecyclerView will use an internal default interpolator. 6143 */ smoothScrollBy(int dx, int dy, int duration, @Nullable Interpolator interpolator)6144 public void smoothScrollBy(int dx, int dy, int duration, 6145 @Nullable Interpolator interpolator) { 6146 6147 // Handle cases where parameter values aren't defined. 6148 if (duration == UNDEFINED_DURATION) { 6149 duration = computeScrollDuration(dx, dy); 6150 } 6151 if (interpolator == null) { 6152 interpolator = sQuinticInterpolator; 6153 } 6154 6155 // If the Interpolator has changed, create a new OverScroller with the new 6156 // interpolator. 6157 if (mInterpolator != interpolator) { 6158 mInterpolator = interpolator; 6159 mOverScroller = new OverScroller(getContext(), interpolator); 6160 } 6161 6162 // Reset the last fling information. 6163 mLastFlingX = mLastFlingY = 0; 6164 6165 // Set to settling state and start scrolling. 6166 setScrollState(SCROLL_STATE_SETTLING); 6167 mOverScroller.startScroll(0, 0, dx, dy, duration); 6168 6169 if (Build.VERSION.SDK_INT < 23) { 6170 // b/64931938 before API 23, startScroll() does not reset getCurX()/getCurY() 6171 // to start values, which causes fillRemainingScrollValues() put in obsolete values 6172 // for LayoutManager.onLayoutChildren(). 6173 mOverScroller.computeScrollOffset(); 6174 } 6175 6176 postOnAnimation(); 6177 } 6178 6179 /** 6180 * Computes of an animated scroll in milliseconds. 6181 * @param dx x distance in pixels. 6182 * @param dy y distance in pixels. 6183 * @return The duration of the animated scroll in milliseconds. 6184 */ computeScrollDuration(int dx, int dy)6185 private int computeScrollDuration(int dx, int dy) { 6186 final int absDx = Math.abs(dx); 6187 final int absDy = Math.abs(dy); 6188 final boolean horizontal = absDx > absDy; 6189 final int containerSize = horizontal ? getWidth() : getHeight(); 6190 6191 float absDelta = (float) (horizontal ? absDx : absDy); 6192 final int duration = (int) (((absDelta / containerSize) + 1) * 300); 6193 6194 return Math.min(duration, MAX_SCROLL_DURATION); 6195 } 6196 stop()6197 public void stop() { 6198 removeCallbacks(this); 6199 mOverScroller.abortAnimation(); 6200 } 6201 6202 } 6203 repositionShadowingViews()6204 void repositionShadowingViews() { 6205 // Fix up shadow views used by change animations 6206 int count = mChildHelper.getChildCount(); 6207 for (int i = 0; i < count; i++) { 6208 View view = mChildHelper.getChildAt(i); 6209 ViewHolder holder = getChildViewHolder(view); 6210 if (holder != null && holder.mShadowingHolder != null) { 6211 View shadowingView = holder.mShadowingHolder.itemView; 6212 int left = view.getLeft(); 6213 int top = view.getTop(); 6214 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { 6215 shadowingView.layout(left, top, 6216 left + shadowingView.getWidth(), 6217 top + shadowingView.getHeight()); 6218 } 6219 } 6220 } 6221 } 6222 6223 private class RecyclerViewDataObserver extends AdapterDataObserver { RecyclerViewDataObserver()6224 RecyclerViewDataObserver() { 6225 } 6226 6227 @Override onChanged()6228 public void onChanged() { 6229 assertNotInLayoutOrScroll(null); 6230 mState.mStructureChanged = true; 6231 6232 processDataSetCompletelyChanged(true); 6233 if (!mAdapterHelper.hasPendingUpdates()) { 6234 requestLayout(); 6235 } 6236 } 6237 6238 @Override onItemRangeChanged(int positionStart, int itemCount, Object payload)6239 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 6240 assertNotInLayoutOrScroll(null); 6241 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { 6242 triggerUpdateProcessor(); 6243 } 6244 } 6245 6246 @Override onItemRangeInserted(int positionStart, int itemCount)6247 public void onItemRangeInserted(int positionStart, int itemCount) { 6248 assertNotInLayoutOrScroll(null); 6249 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 6250 triggerUpdateProcessor(); 6251 } 6252 } 6253 6254 @Override onItemRangeRemoved(int positionStart, int itemCount)6255 public void onItemRangeRemoved(int positionStart, int itemCount) { 6256 assertNotInLayoutOrScroll(null); 6257 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 6258 triggerUpdateProcessor(); 6259 } 6260 } 6261 6262 @Override onItemRangeMoved(int fromPosition, int toPosition, int itemCount)6263 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 6264 assertNotInLayoutOrScroll(null); 6265 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 6266 triggerUpdateProcessor(); 6267 } 6268 } 6269 triggerUpdateProcessor()6270 void triggerUpdateProcessor() { 6271 if (mHasFixedSize && mIsAttached) { 6272 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); 6273 } else { 6274 mAdapterUpdateDuringMeasure = true; 6275 requestLayout(); 6276 } 6277 } 6278 6279 @Override onStateRestorationPolicyChanged()6280 public void onStateRestorationPolicyChanged() { 6281 if (mPendingSavedState == null) { 6282 return; 6283 } 6284 // If there is a pending saved state and the new mode requires us to restore it, 6285 // we'll request a layout which will call the adapter to see if it can restore state 6286 // and trigger state restoration 6287 Adapter<?> adapter = mAdapter; 6288 if (adapter != null && adapter.canRestoreState()) { 6289 requestLayout(); 6290 } 6291 } 6292 } 6293 6294 /** 6295 * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews. 6296 * 6297 * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory) 6298 */ 6299 public static class EdgeEffectFactory { 6300 6301 @Retention(RetentionPolicy.SOURCE) 6302 @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM}) 6303 public @interface EdgeDirection { 6304 } 6305 6306 /** 6307 * Direction constant for the left edge 6308 */ 6309 public static final int DIRECTION_LEFT = 0; 6310 6311 /** 6312 * Direction constant for the top edge 6313 */ 6314 public static final int DIRECTION_TOP = 1; 6315 6316 /** 6317 * Direction constant for the right edge 6318 */ 6319 public static final int DIRECTION_RIGHT = 2; 6320 6321 /** 6322 * Direction constant for the bottom edge 6323 */ 6324 public static final int DIRECTION_BOTTOM = 3; 6325 6326 /** 6327 * Create a new EdgeEffect for the provided direction. 6328 */ createEdgeEffect(@onNull RecyclerView view, @EdgeDirection int direction)6329 protected @NonNull EdgeEffect createEdgeEffect(@NonNull RecyclerView view, 6330 @EdgeDirection int direction) { 6331 return new EdgeEffect(view.getContext()); 6332 } 6333 } 6334 6335 /** 6336 * The default EdgeEffectFactory sets the edge effect type of the EdgeEffect. 6337 */ 6338 static class StretchEdgeEffectFactory extends EdgeEffectFactory { 6339 @Override createEdgeEffect(@onNull RecyclerView view, int direction)6340 protected @NonNull EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) { 6341 return new EdgeEffect(view.getContext()); 6342 } 6343 } 6344 6345 /** 6346 * RecycledViewPool lets you share Views between multiple RecyclerViews. 6347 * <p> 6348 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool 6349 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. 6350 * <p> 6351 * RecyclerView automatically creates a pool for itself if you don't provide one. 6352 */ 6353 public static class RecycledViewPool { 6354 private static final int DEFAULT_MAX_SCRAP = 5; 6355 6356 /** 6357 * Tracks both pooled holders, as well as create/bind timing metadata for the given type. 6358 * 6359 * Note that this tracks running averages of create/bind time across all RecyclerViews 6360 * (and, indirectly, Adapters) that use this pool. 6361 * 6362 * 1) This enables us to track average create and bind times across multiple adapters. Even 6363 * though create (and especially bind) may behave differently for different Adapter 6364 * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. 6365 * 6366 * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return 6367 * false for all other views of its type for the same deadline. This prevents items 6368 * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. 6369 */ 6370 static class ScrapData { 6371 final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); 6372 int mMaxScrap = DEFAULT_MAX_SCRAP; 6373 long mCreateRunningAverageNs = 0; 6374 long mBindRunningAverageNs = 0; 6375 } 6376 6377 SparseArray<ScrapData> mScrap = new SparseArray<>(); 6378 6379 /** 6380 * Attach counts for clearing (that is, emptying the pool when there are no adapters 6381 * attached) and for PoolingContainer release are tracked separately to maintain the 6382 * historical behavior of this functionality. 6383 * 6384 * The count for clearing is inaccurate in certain scenarios: for instance, if a 6385 * RecyclerView is removed from the view hierarchy and thrown away to be GCed, the 6386 * attach count will never be correspondingly decreased. However, it has been this way 6387 * for years without any complaints, so we are not going to potentially increase the 6388 * number of scenarios where the pool would be cleared. 6389 * 6390 * The attached adapters for PoolingContainer purposes strives to be more accurate, as 6391 * it will be decremented whenever a RecyclerView is detached from the window. This 6392 * could potentially be inaccurate in the unlikely event that someone is manually driving 6393 * a detached RecyclerView by calling measure, layout, draw, etc. However, the 6394 * implementation of {@link RecyclerView#onDetachedFromWindow()} suggests this is not the 6395 * only unexpected behavior that doing so might provoke, so this should be acceptable. 6396 */ 6397 int mAttachCountForClearing = 0; 6398 6399 /** 6400 * The set of adapters for PoolingContainer release purposes 6401 * 6402 * @see #mAttachCountForClearing 6403 */ 6404 Set<Adapter<?>> mAttachedAdaptersForPoolingContainer = 6405 Collections.newSetFromMap(new IdentityHashMap<>()); 6406 6407 /** 6408 * Discard all ViewHolders. 6409 */ clear()6410 public void clear() { 6411 for (int i = 0; i < mScrap.size(); i++) { 6412 ScrapData data = mScrap.valueAt(i); 6413 for (ViewHolder scrap: data.mScrapHeap) { 6414 PoolingContainer.callPoolingContainerOnRelease(scrap.itemView); 6415 } 6416 data.mScrapHeap.clear(); 6417 } 6418 } 6419 6420 /** 6421 * Sets the maximum number of ViewHolders to hold in the pool before discarding. 6422 * 6423 * @param viewType ViewHolder Type 6424 * @param max Maximum number 6425 */ setMaxRecycledViews(int viewType, int max)6426 public void setMaxRecycledViews(int viewType, int max) { 6427 ScrapData scrapData = getScrapDataForType(viewType); 6428 scrapData.mMaxScrap = max; 6429 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 6430 while (scrapHeap.size() > max) { 6431 scrapHeap.remove(scrapHeap.size() - 1); 6432 } 6433 } 6434 6435 /** 6436 * Returns the current number of Views held by the RecycledViewPool of the given view type. 6437 */ getRecycledViewCount(int viewType)6438 public int getRecycledViewCount(int viewType) { 6439 return getScrapDataForType(viewType).mScrapHeap.size(); 6440 } 6441 6442 /** 6443 * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are 6444 * present. 6445 * 6446 * @param viewType ViewHolder type. 6447 * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none 6448 * are present. 6449 */ getRecycledView(int viewType)6450 public @Nullable ViewHolder getRecycledView(int viewType) { 6451 final ScrapData scrapData = mScrap.get(viewType); 6452 if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { 6453 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; 6454 for (int i = scrapHeap.size() - 1; i >= 0; i--) { 6455 if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) { 6456 return scrapHeap.remove(i); 6457 } 6458 } 6459 } 6460 return null; 6461 } 6462 6463 /** 6464 * Total number of ViewHolders held by the pool. 6465 * 6466 * @return Number of ViewHolders held by the pool. 6467 */ size()6468 int size() { 6469 int count = 0; 6470 for (int i = 0; i < mScrap.size(); i++) { 6471 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap; 6472 if (viewHolders != null) { 6473 count += viewHolders.size(); 6474 } 6475 } 6476 return count; 6477 } 6478 6479 /** 6480 * Add a scrap ViewHolder to the pool. 6481 * <p> 6482 * If the pool is already full for that ViewHolder's type, it will be immediately discarded. 6483 * 6484 * @param scrap ViewHolder to be added to the pool. 6485 */ putRecycledView(ViewHolder scrap)6486 public void putRecycledView(ViewHolder scrap) { 6487 final int viewType = scrap.getItemViewType(); 6488 final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; 6489 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { 6490 PoolingContainer.callPoolingContainerOnRelease(scrap.itemView); 6491 return; 6492 } 6493 if (sDebugAssertionsEnabled && scrapHeap.contains(scrap)) { 6494 throw new IllegalArgumentException("this scrap item already exists"); 6495 } 6496 scrap.resetInternal(); 6497 scrapHeap.add(scrap); 6498 } 6499 runningAverage(long oldAverage, long newValue)6500 long runningAverage(long oldAverage, long newValue) { 6501 if (oldAverage == 0) { 6502 return newValue; 6503 } 6504 return (oldAverage / 4 * 3) + (newValue / 4); 6505 } 6506 factorInCreateTime(int viewType, long createTimeNs)6507 void factorInCreateTime(int viewType, long createTimeNs) { 6508 ScrapData scrapData = getScrapDataForType(viewType); 6509 scrapData.mCreateRunningAverageNs = runningAverage( 6510 scrapData.mCreateRunningAverageNs, createTimeNs); 6511 } 6512 factorInBindTime(int viewType, long bindTimeNs)6513 void factorInBindTime(int viewType, long bindTimeNs) { 6514 ScrapData scrapData = getScrapDataForType(viewType); 6515 scrapData.mBindRunningAverageNs = runningAverage( 6516 scrapData.mBindRunningAverageNs, bindTimeNs); 6517 } 6518 willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs)6519 boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { 6520 long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; 6521 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 6522 } 6523 willBindInTime(int viewType, long approxCurrentNs, long deadlineNs)6524 boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { 6525 long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; 6526 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); 6527 } 6528 attach()6529 void attach() { 6530 mAttachCountForClearing++; 6531 } 6532 detach()6533 void detach() { 6534 mAttachCountForClearing--; 6535 } 6536 6537 /** 6538 * Adds this adapter to the set of adapters being tracked for PoolingContainer release 6539 * purposes. This method may validly be called multiple times for a given adapter. 6540 * Additional calls to this method for an already-attached adapter are a no-op. 6541 * 6542 * @param adapter the adapter to ensure is in the set 6543 */ attachForPoolingContainer(@onNull Adapter<?> adapter)6544 void attachForPoolingContainer(@NonNull Adapter<?> adapter) { 6545 mAttachedAdaptersForPoolingContainer.add(adapter); 6546 } 6547 6548 /** 6549 * Removes this adapter from the set of adapters being tracked for PoolingContainer 6550 * release purposes. This method may validly be called multiple times for a given adapter. 6551 + Additional calls to this method for an already-detached adapter are a no-op. 6552 * 6553 * @param adapter the adapter to be removed from the set 6554 * @param isBeingReplaced {@code true} if this detach is immediately preceding a call to 6555 * {@link #attachForPoolingContainer(Adapter)} and 6556 * {@link PoolingContainerListener#onRelease()} should not be triggered, or false otherwise 6557 */ detachForPoolingContainer(@onNull Adapter<?> adapter, boolean isBeingReplaced)6558 void detachForPoolingContainer(@NonNull Adapter<?> adapter, boolean isBeingReplaced) { 6559 mAttachedAdaptersForPoolingContainer.remove(adapter); 6560 if (mAttachedAdaptersForPoolingContainer.size() == 0 && !isBeingReplaced) { 6561 for (int keyIndex = 0; keyIndex < mScrap.size(); keyIndex++) { 6562 ArrayList<ViewHolder> scrapHeap = mScrap.get(mScrap.keyAt(keyIndex)).mScrapHeap; 6563 for (int i = 0; i < scrapHeap.size(); i++) { 6564 PoolingContainer.callPoolingContainerOnRelease( 6565 scrapHeap.get(i).itemView 6566 ); 6567 } 6568 } 6569 } 6570 } 6571 6572 /** 6573 * Detaches the old adapter and attaches the new one. 6574 * <p> 6575 * RecycledViewPool will clear its cache if it has only one adapter attached and the new 6576 * adapter uses a different ViewHolder than the oldAdapter. 6577 * 6578 * @param oldAdapter The previous adapter instance. Will be detached. 6579 * @param newAdapter The new adapter instance. Will be attached. 6580 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same 6581 * ViewHolder and view types. 6582 */ onAdapterChanged(Adapter<?> oldAdapter, Adapter<?> newAdapter, boolean compatibleWithPrevious)6583 void onAdapterChanged(Adapter<?> oldAdapter, Adapter<?> newAdapter, 6584 boolean compatibleWithPrevious) { 6585 if (oldAdapter != null) { 6586 detach(); 6587 } 6588 if (!compatibleWithPrevious && mAttachCountForClearing == 0) { 6589 clear(); 6590 } 6591 if (newAdapter != null) { 6592 attach(); 6593 } 6594 } 6595 getScrapDataForType(int viewType)6596 private ScrapData getScrapDataForType(int viewType) { 6597 ScrapData scrapData = mScrap.get(viewType); 6598 if (scrapData == null) { 6599 scrapData = new ScrapData(); 6600 mScrap.put(viewType, scrapData); 6601 } 6602 return scrapData; 6603 } 6604 } 6605 6606 /** 6607 * Utility method for finding an internal RecyclerView, if present 6608 */ findNestedRecyclerView(@onNull View view)6609 static @Nullable RecyclerView findNestedRecyclerView(@NonNull View view) { 6610 if (!(view instanceof ViewGroup)) { 6611 return null; 6612 } 6613 if (view instanceof RecyclerView) { 6614 return (RecyclerView) view; 6615 } 6616 final ViewGroup parent = (ViewGroup) view; 6617 final int count = parent.getChildCount(); 6618 for (int i = 0; i < count; i++) { 6619 final View child = parent.getChildAt(i); 6620 final RecyclerView descendant = findNestedRecyclerView(child); 6621 if (descendant != null) { 6622 return descendant; 6623 } 6624 } 6625 return null; 6626 } 6627 6628 /** 6629 * Utility method for clearing holder's internal RecyclerView, if present 6630 */ clearNestedRecyclerViewIfNotNested(@onNull ViewHolder holder)6631 static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { 6632 if (holder.mNestedRecyclerView != null) { 6633 View item = holder.mNestedRecyclerView.get(); 6634 while (item != null) { 6635 if (item == holder.itemView) { 6636 return; // match found, don't need to clear 6637 } 6638 6639 ViewParent parent = item.getParent(); 6640 if (parent instanceof View) { 6641 item = (View) parent; 6642 } else { 6643 item = null; 6644 } 6645 } 6646 holder.mNestedRecyclerView = null; // not nested 6647 } 6648 } 6649 6650 /** 6651 * Time base for deadline-aware work scheduling. Overridable for testing. 6652 * 6653 * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling 6654 * isn't relevant. 6655 */ getNanoTime()6656 long getNanoTime() { 6657 if (ALLOW_THREAD_GAP_WORK) { 6658 return System.nanoTime(); 6659 } else { 6660 return 0; 6661 } 6662 } 6663 6664 /** 6665 * A Recycler is responsible for managing scrapped or detached item views for reuse. 6666 * 6667 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 6668 * that has been marked for removal or reuse.</p> 6669 * 6670 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 6671 * an adapter's data set representing the data at a given position or item ID. 6672 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 6673 * If not, the view can be quickly reused by the LayoutManager with no further work. 6674 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 6675 * may be repositioned by a LayoutManager without remeasurement.</p> 6676 */ 6677 public final class Recycler { 6678 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); 6679 ArrayList<ViewHolder> mChangedScrap = null; 6680 6681 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 6682 6683 private final List<ViewHolder> 6684 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); 6685 6686 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; 6687 int mViewCacheMax = DEFAULT_CACHE_SIZE; 6688 6689 RecycledViewPool mRecyclerPool; 6690 6691 private ViewCacheExtension mViewCacheExtension; 6692 6693 static final int DEFAULT_CACHE_SIZE = 2; 6694 6695 /** 6696 * Clear scrap views out of this recycler. Detached views contained within a 6697 * recycled view pool will remain. 6698 */ clear()6699 public void clear() { 6700 mAttachedScrap.clear(); 6701 recycleAndClearCachedViews(); 6702 } 6703 6704 /** 6705 * Set the maximum number of detached, valid views we should retain for later use. 6706 * 6707 * @param viewCount Number of views to keep before sending views to the shared pool 6708 */ setViewCacheSize(int viewCount)6709 public void setViewCacheSize(int viewCount) { 6710 mRequestedCacheMax = viewCount; 6711 updateViewCacheSize(); 6712 } 6713 updateViewCacheSize()6714 void updateViewCacheSize() { 6715 int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; 6716 mViewCacheMax = mRequestedCacheMax + extraCache; 6717 6718 // first, try the views that can be recycled 6719 for (int i = mCachedViews.size() - 1; 6720 i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { 6721 recycleCachedViewAt(i); 6722 } 6723 } 6724 6725 /** 6726 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. 6727 * 6728 * @return List of ViewHolders in the scrap list. 6729 */ getScrapList()6730 public @NonNull List<ViewHolder> getScrapList() { 6731 return mUnmodifiableAttachedScrap; 6732 } 6733 6734 /** 6735 * Helper method for getViewForPosition. 6736 * <p> 6737 * Checks whether a given view holder can be used for the provided position. 6738 * 6739 * @param holder ViewHolder 6740 * @return true if ViewHolder matches the provided position, false otherwise 6741 */ validateViewHolderForOffsetPosition(ViewHolder holder)6742 boolean validateViewHolderForOffsetPosition(ViewHolder holder) { 6743 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore 6744 // if it is not removed, verify the type and id. 6745 if (holder.isRemoved()) { 6746 if (sDebugAssertionsEnabled && !mState.isPreLayout()) { 6747 throw new IllegalStateException("should not receive a removed view unless it" 6748 + " is pre layout" + exceptionLabel()); 6749 } 6750 return mState.isPreLayout(); 6751 } 6752 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { 6753 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " 6754 + "adapter position" + holder + exceptionLabel()); 6755 } 6756 if (!mState.isPreLayout()) { 6757 // don't check type if it is pre-layout. 6758 final int type = mAdapter.getItemViewType(holder.mPosition); 6759 if (type != holder.getItemViewType()) { 6760 return false; 6761 } 6762 } 6763 if (mAdapter.hasStableIds()) { 6764 return holder.getItemId() == mAdapter.getItemId(holder.mPosition); 6765 } 6766 return true; 6767 } 6768 6769 /** 6770 * Attempts to bind view, and account for relevant timing information. If 6771 * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. 6772 * 6773 * @param holder Holder to be bound. 6774 * @param offsetPosition Position of item to be bound. 6775 * @param position Pre-layout position of item to be bound. 6776 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 6777 * complete. If FOREVER_NS is passed, this method will not fail to 6778 * bind the holder. 6779 */ 6780 @SuppressWarnings("unchecked") tryBindViewHolderByDeadline(@onNull ViewHolder holder, int offsetPosition, int position, long deadlineNs)6781 private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, 6782 int position, long deadlineNs) { 6783 holder.mBindingAdapter = null; 6784 holder.mOwnerRecyclerView = RecyclerView.this; 6785 final int viewType = holder.getItemViewType(); 6786 long startBindNs = getNanoTime(); 6787 if (deadlineNs != FOREVER_NS 6788 && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { 6789 // abort - we have a deadline we can't meet 6790 return false; 6791 } 6792 6793 // Holders being bound should be either fully attached or fully detached. 6794 // We don't want to bind with views that are temporarily detached, because that 6795 // creates a situation in which they are unable to reason about their attach state 6796 // properly. 6797 // For example, isAttachedToWindow will return true, but the itemView will lack a 6798 // parent. This breaks, among other possible issues, anything involving traversing 6799 // the view tree, such as ViewTreeLifecycleOwner. 6800 // Thus, we temporarily reattach any temp-detached holders for the bind operation. 6801 // See https://issuetracker.google.com/265347515 for additional details on problems 6802 // resulting from this 6803 boolean reattachedForBind = false; 6804 if (holder.isTmpDetached()) { 6805 attachViewToParent(holder.itemView, getChildCount(), 6806 holder.itemView.getLayoutParams()); 6807 reattachedForBind = true; 6808 } 6809 6810 mAdapter.bindViewHolder(holder, offsetPosition); 6811 6812 if (reattachedForBind) { 6813 detachViewFromParent(holder.itemView); 6814 } 6815 6816 long endBindNs = getNanoTime(); 6817 mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); 6818 attachAccessibilityDelegateOnBind(holder); 6819 if (mState.isPreLayout()) { 6820 holder.mPreLayoutPosition = position; 6821 } 6822 return true; 6823 } 6824 6825 /** 6826 * Binds the given View to the position. The View can be a View previously retrieved via 6827 * {@link #getViewForPosition(int)} or created by 6828 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. 6829 * <p> 6830 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} 6831 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who 6832 * wants to handle its own recycling logic. 6833 * <p> 6834 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so 6835 * you don't need to call this method unless you want to bind this View to another position. 6836 * 6837 * @param view The view to update. 6838 * @param position The position of the item to bind to this View. 6839 */ bindViewToPosition(@onNull View view, int position)6840 public void bindViewToPosition(@NonNull View view, int position) { 6841 ViewHolder holder = getChildViewHolderInt(view); 6842 if (holder == null) { 6843 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" 6844 + " pass arbitrary views to this method, they should be created by the " 6845 + "Adapter" + exceptionLabel()); 6846 } 6847 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 6848 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 6849 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 6850 + "position " + position + "(offset:" + offsetPosition + ")." 6851 + "state:" + mState.getItemCount() + exceptionLabel()); 6852 } 6853 tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); 6854 6855 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 6856 final LayoutParams rvLayoutParams; 6857 if (lp == null) { 6858 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 6859 holder.itemView.setLayoutParams(rvLayoutParams); 6860 } else if (!checkLayoutParams(lp)) { 6861 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 6862 holder.itemView.setLayoutParams(rvLayoutParams); 6863 } else { 6864 rvLayoutParams = (LayoutParams) lp; 6865 } 6866 6867 rvLayoutParams.mInsetsDirty = true; 6868 rvLayoutParams.mViewHolder = holder; 6869 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; 6870 } 6871 6872 /** 6873 * RecyclerView provides artificial position range (item count) in pre-layout state and 6874 * automatically maps these positions to {@link Adapter} positions when 6875 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. 6876 * <p> 6877 * Usually, LayoutManager does not need to worry about this. However, in some cases, your 6878 * LayoutManager may need to call some custom component with item positions in which 6879 * case you need the actual adapter position instead of the pre layout position. You 6880 * can use this method to convert a pre-layout position to adapter (post layout) position. 6881 * <p> 6882 * Note that if the provided position belongs to a deleted ViewHolder, this method will 6883 * return -1. 6884 * <p> 6885 * Calling this method in post-layout state returns the same value back. 6886 * 6887 * @param position The pre-layout position to convert. Must be greater or equal to 0 and 6888 * less than {@link State#getItemCount()}. 6889 */ convertPreLayoutPositionToPostLayout(int position)6890 public int convertPreLayoutPositionToPostLayout(int position) { 6891 if (position < 0 || position >= mState.getItemCount()) { 6892 throw new IndexOutOfBoundsException("invalid position " + position + ". State " 6893 + "item count is " + mState.getItemCount() + exceptionLabel()); 6894 } 6895 if (!mState.isPreLayout()) { 6896 return position; 6897 } 6898 return mAdapterHelper.findPositionOffset(position); 6899 } 6900 6901 /** 6902 * Obtain a view initialized for the given position. 6903 * 6904 * This method should be used by {@link LayoutManager} implementations to obtain 6905 * views to represent data from an {@link Adapter}. 6906 * <p> 6907 * The Recycler may reuse a scrap or detached view from a shared pool if one is 6908 * available for the correct view type. If the adapter has not indicated that the 6909 * data at the given position has changed, the Recycler will attempt to hand back 6910 * a scrap view that was previously initialized for that data without rebinding. 6911 * 6912 * @param position Position to obtain a view for 6913 * @return A view representing the data at <code>position</code> from <code>adapter</code> 6914 */ getViewForPosition(int position)6915 public @NonNull View getViewForPosition(int position) { 6916 return getViewForPosition(position, false); 6917 } 6918 getViewForPosition(int position, boolean dryRun)6919 View getViewForPosition(int position, boolean dryRun) { 6920 return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; 6921 } 6922 6923 /** 6924 * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, 6925 * cache, the RecycledViewPool, or creating it directly. 6926 * <p> 6927 * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return 6928 * rather than constructing or binding a ViewHolder if it doesn't think it has time. 6929 * If a ViewHolder must be constructed and not enough time remains, null is returned. If a 6930 * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is 6931 * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. 6932 * 6933 * @param position Position of ViewHolder to be returned. 6934 * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ 6935 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should 6936 * complete. If FOREVER_NS is passed, this method will not fail to 6937 * create/bind the holder if needed. 6938 * @return ViewHolder for requested position 6939 */ tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)6940 @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, 6941 boolean dryRun, long deadlineNs) { 6942 if (position < 0 || position >= mState.getItemCount()) { 6943 throw new IndexOutOfBoundsException("Invalid item position " + position 6944 + "(" + position + "). Item count:" + mState.getItemCount() 6945 + exceptionLabel()); 6946 } 6947 boolean fromScrapOrHiddenOrCache = false; 6948 ViewHolder holder = null; 6949 // 0) If there is a changed scrap, try to find from there 6950 if (mState.isPreLayout()) { 6951 holder = getChangedScrapViewForPosition(position); 6952 fromScrapOrHiddenOrCache = holder != null; 6953 } 6954 // 1) Find by position from scrap/hidden list/cache 6955 if (holder == null) { 6956 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); 6957 if (holder != null) { 6958 if (!validateViewHolderForOffsetPosition(holder)) { 6959 // recycle holder (and unscrap if relevant) since it can't be used 6960 if (!dryRun) { 6961 // we would like to recycle this but need to make sure it is not used by 6962 // animation logic etc. 6963 holder.addFlags(ViewHolder.FLAG_INVALID); 6964 if (holder.isScrap()) { 6965 removeDetachedView(holder.itemView, false); 6966 holder.unScrap(); 6967 } else if (holder.wasReturnedFromScrap()) { 6968 holder.clearReturnedFromScrapFlag(); 6969 } 6970 recycleViewHolderInternal(holder); 6971 } 6972 holder = null; 6973 } else { 6974 fromScrapOrHiddenOrCache = true; 6975 } 6976 } 6977 } 6978 if (holder == null) { 6979 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 6980 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { 6981 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " 6982 + "position " + position + "(offset:" + offsetPosition + ")." 6983 + "state:" + mState.getItemCount() + exceptionLabel()); 6984 } 6985 6986 final int type = mAdapter.getItemViewType(offsetPosition); 6987 // 2) Find from scrap/cache via stable ids, if exists 6988 if (mAdapter.hasStableIds()) { 6989 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), 6990 type, dryRun); 6991 if (holder != null) { 6992 // update position 6993 holder.mPosition = offsetPosition; 6994 fromScrapOrHiddenOrCache = true; 6995 } 6996 } 6997 if (holder == null && mViewCacheExtension != null) { 6998 // We are NOT sending the offsetPosition because LayoutManager does not 6999 // know it. 7000 final View view = mViewCacheExtension 7001 .getViewForPositionAndType(this, position, type); 7002 if (view != null) { 7003 holder = getChildViewHolder(view); 7004 if (holder == null) { 7005 throw new IllegalArgumentException("getViewForPositionAndType returned" 7006 + " a view which does not have a ViewHolder" 7007 + exceptionLabel()); 7008 } else if (holder.shouldIgnore()) { 7009 throw new IllegalArgumentException("getViewForPositionAndType returned" 7010 + " a view that is ignored. You must call stopIgnoring before" 7011 + " returning this view." + exceptionLabel()); 7012 } 7013 } 7014 } 7015 if (holder == null) { // fallback to pool 7016 if (sVerboseLoggingEnabled) { 7017 Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" 7018 + position + ") fetching from shared pool"); 7019 } 7020 holder = getRecycledViewPool().getRecycledView(type); 7021 if (holder != null) { 7022 holder.resetInternal(); 7023 if (FORCE_INVALIDATE_DISPLAY_LIST) { 7024 invalidateDisplayListInt(holder); 7025 } 7026 } 7027 } 7028 if (holder == null) { 7029 long start = getNanoTime(); 7030 if (deadlineNs != FOREVER_NS 7031 && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { 7032 // abort - we have a deadline we can't meet 7033 return null; 7034 } 7035 holder = mAdapter.createViewHolder(RecyclerView.this, type); 7036 if (ALLOW_THREAD_GAP_WORK) { 7037 // only bother finding nested RV if prefetching 7038 RecyclerView innerView = findNestedRecyclerView(holder.itemView); 7039 if (innerView != null) { 7040 holder.mNestedRecyclerView = new WeakReference<>(innerView); 7041 } 7042 } 7043 7044 long end = getNanoTime(); 7045 mRecyclerPool.factorInCreateTime(type, end - start); 7046 if (sVerboseLoggingEnabled) { 7047 Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); 7048 } 7049 } 7050 } 7051 7052 // This is very ugly but the only place we can grab this information 7053 // before the View is rebound and returned to the LayoutManager for post layout ops. 7054 // We don't need this in pre-layout since the VH is not updated by the LM. 7055 if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder 7056 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { 7057 holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 7058 if (mState.mRunSimpleAnimations) { 7059 int changeFlags = ItemAnimator 7060 .buildAdapterChangeFlagsForAnimations(holder); 7061 changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; 7062 final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, 7063 holder, changeFlags, holder.getUnmodifiedPayloads()); 7064 recordAnimationInfoIfBouncedHiddenView(holder, info); 7065 } 7066 } 7067 7068 boolean bound = false; 7069 if (mState.isPreLayout() && holder.isBound()) { 7070 // do not update unless we absolutely have to. 7071 holder.mPreLayoutPosition = position; 7072 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { 7073 if (sDebugAssertionsEnabled && holder.isRemoved()) { 7074 throw new IllegalStateException("Removed holder should be bound and it should" 7075 + " come here only in pre-layout. Holder: " + holder 7076 + exceptionLabel()); 7077 } 7078 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 7079 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); 7080 } 7081 7082 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 7083 final LayoutParams rvLayoutParams; 7084 if (lp == null) { 7085 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); 7086 holder.itemView.setLayoutParams(rvLayoutParams); 7087 } else if (!checkLayoutParams(lp)) { 7088 rvLayoutParams = (LayoutParams) generateLayoutParams(lp); 7089 holder.itemView.setLayoutParams(rvLayoutParams); 7090 } else { 7091 rvLayoutParams = (LayoutParams) lp; 7092 } 7093 rvLayoutParams.mViewHolder = holder; 7094 rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; 7095 return holder; 7096 } 7097 attachAccessibilityDelegateOnBind(ViewHolder holder)7098 private void attachAccessibilityDelegateOnBind(ViewHolder holder) { 7099 if (isAccessibilityEnabled()) { 7100 final View itemView = holder.itemView; 7101 if (itemView.getImportantForAccessibility() 7102 == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 7103 itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 7104 } 7105 if (mAccessibilityDelegate == null) { 7106 return; 7107 } 7108 AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate(); 7109 if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) { 7110 // If there was already an a11y delegate set on the itemView, store it in the 7111 // itemDelegate and then set the itemDelegate as the a11y delegate. 7112 ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate) 7113 .saveOriginalDelegate(itemView); 7114 } 7115 ViewCompat.setAccessibilityDelegate(itemView, itemDelegate); 7116 } 7117 } 7118 invalidateDisplayListInt(ViewHolder holder)7119 private void invalidateDisplayListInt(ViewHolder holder) { 7120 if (holder.itemView instanceof ViewGroup) { 7121 invalidateDisplayListInt((ViewGroup) holder.itemView, false); 7122 } 7123 } 7124 invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis)7125 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { 7126 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 7127 final View view = viewGroup.getChildAt(i); 7128 if (view instanceof ViewGroup) { 7129 invalidateDisplayListInt((ViewGroup) view, true); 7130 } 7131 } 7132 if (!invalidateThis) { 7133 return; 7134 } 7135 // we need to force it to become invisible 7136 if (viewGroup.getVisibility() == View.INVISIBLE) { 7137 viewGroup.setVisibility(View.VISIBLE); 7138 viewGroup.setVisibility(View.INVISIBLE); 7139 } else { 7140 final int visibility = viewGroup.getVisibility(); 7141 viewGroup.setVisibility(View.INVISIBLE); 7142 viewGroup.setVisibility(visibility); 7143 } 7144 } 7145 7146 /** 7147 * Recycle a detached view. The specified view will be added to a pool of views 7148 * for later rebinding and reuse. 7149 * 7150 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the 7151 * View is scrapped, it will be removed from scrap list.</p> 7152 * 7153 * @param view Removed view for recycling 7154 * @see LayoutManager#removeAndRecycleView(View, Recycler) 7155 */ recycleView(@onNull View view)7156 public void recycleView(@NonNull View view) { 7157 // This public recycle method tries to make view recycle-able since layout manager 7158 // intended to recycle this view (e.g. even if it is in scrap or change cache) 7159 ViewHolder holder = getChildViewHolderInt(view); 7160 if (holder.isTmpDetached()) { 7161 removeDetachedView(view, false); 7162 } 7163 if (holder.isScrap()) { 7164 holder.unScrap(); 7165 } else if (holder.wasReturnedFromScrap()) { 7166 holder.clearReturnedFromScrapFlag(); 7167 } 7168 recycleViewHolderInternal(holder); 7169 // If the ViewHolder is running ItemAnimator, we want the recycleView() in scroll pass 7170 // to stop the ItemAnimator and put ViewHolder back in cache or Pool. 7171 // There are three situations: 7172 // 1. If the custom Adapter clears ViewPropertyAnimator in view detach like the 7173 // leanback (TV) app does, the ItemAnimator is likely to be stopped and 7174 // recycleViewHolderInternal will succeed. 7175 // 2. If the custom Adapter clears ViewPropertyAnimator, but the ItemAnimator uses 7176 // "pending runnable" and ViewPropertyAnimator has not started yet, the ItemAnimator 7177 // on the view will not be cleared. See b/73552923. 7178 // 3. If the custom Adapter does not clear ViewPropertyAnimator in view detach, the 7179 // ItemAnimator will not be cleared. 7180 // Since both 2&3 lead to failure of recycleViewHolderInternal(), we just explicitly end 7181 // the ItemAnimator, the callback of ItemAnimator.endAnimations() will recycle the View. 7182 // 7183 // Note the order: we must call endAnimation() after recycleViewHolderInternal() 7184 // to avoid recycle twice. If ViewHolder isRecyclable is false, 7185 // recycleViewHolderInternal() will not recycle it, endAnimation() will reset 7186 // isRecyclable flag and recycle the view. 7187 if (mItemAnimator != null && !holder.isRecyclable()) { 7188 mItemAnimator.endAnimation(holder); 7189 } 7190 } 7191 recycleAndClearCachedViews()7192 void recycleAndClearCachedViews() { 7193 final int count = mCachedViews.size(); 7194 for (int i = count - 1; i >= 0; i--) { 7195 recycleCachedViewAt(i); 7196 } 7197 mCachedViews.clear(); 7198 if (ALLOW_THREAD_GAP_WORK) { 7199 mPrefetchRegistry.clearPrefetchPositions(); 7200 } 7201 } 7202 7203 /** 7204 * Recycles a cached view and removes the view from the list. Views are added to cache 7205 * if and only if they are recyclable, so this method does not check it again. 7206 * <p> 7207 * A small exception to this rule is when the view does not have an animator reference 7208 * but transient state is true (due to animations created outside ItemAnimator). In that 7209 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is 7210 * still recyclable since Adapter wants to do so. 7211 * 7212 * @param cachedViewIndex The index of the view in cached views list 7213 */ recycleCachedViewAt(int cachedViewIndex)7214 void recycleCachedViewAt(int cachedViewIndex) { 7215 if (sVerboseLoggingEnabled) { 7216 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); 7217 } 7218 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); 7219 if (sVerboseLoggingEnabled) { 7220 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); 7221 } 7222 addViewHolderToRecycledViewPool(viewHolder, true); 7223 mCachedViews.remove(cachedViewIndex); 7224 } 7225 7226 /** 7227 * internal implementation checks if view is scrapped or attached and throws an exception 7228 * if so. 7229 * Public version un-scraps before calling recycle. 7230 */ recycleViewHolderInternal(ViewHolder holder)7231 void recycleViewHolderInternal(ViewHolder holder) { 7232 if (holder.isScrap() || holder.itemView.getParent() != null) { 7233 throw new IllegalArgumentException( 7234 "Scrapped or attached views may not be recycled. isScrap:" 7235 + holder.isScrap() + " isAttached:" 7236 + (holder.itemView.getParent() != null) + exceptionLabel()); 7237 } 7238 7239 if (holder.isTmpDetached()) { 7240 throw new IllegalArgumentException("Tmp detached view should be removed " 7241 + "from RecyclerView before it can be recycled: " + holder 7242 + exceptionLabel()); 7243 } 7244 7245 if (holder.shouldIgnore()) { 7246 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" 7247 + " should first call stopIgnoringView(view) before calling recycle." 7248 + exceptionLabel()); 7249 } 7250 final boolean transientStatePreventsRecycling = holder 7251 .doesTransientStatePreventRecycling(); 7252 @SuppressWarnings("unchecked") final boolean forceRecycle = mAdapter != null 7253 && transientStatePreventsRecycling 7254 && mAdapter.onFailedToRecycleView(holder); 7255 boolean cached = false; 7256 boolean recycled = false; 7257 if (sDebugAssertionsEnabled && mCachedViews.contains(holder)) { 7258 throw new IllegalArgumentException("cached view received recycle internal? " 7259 + holder + exceptionLabel()); 7260 } 7261 if (forceRecycle || holder.isRecyclable()) { 7262 if (mViewCacheMax > 0 7263 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 7264 | ViewHolder.FLAG_REMOVED 7265 | ViewHolder.FLAG_UPDATE 7266 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { 7267 // Retire oldest cached view 7268 int cachedViewSize = mCachedViews.size(); 7269 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { 7270 recycleCachedViewAt(0); 7271 cachedViewSize--; 7272 } 7273 7274 int targetCacheIndex = cachedViewSize; 7275 if (ALLOW_THREAD_GAP_WORK 7276 && cachedViewSize > 0 7277 && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { 7278 // when adding the view, skip past most recently prefetched views 7279 int cacheIndex = cachedViewSize - 1; 7280 while (cacheIndex >= 0) { 7281 int cachedPos = mCachedViews.get(cacheIndex).mPosition; 7282 if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { 7283 break; 7284 } 7285 cacheIndex--; 7286 } 7287 targetCacheIndex = cacheIndex + 1; 7288 } 7289 mCachedViews.add(targetCacheIndex, holder); 7290 cached = true; 7291 } 7292 if (!cached) { 7293 addViewHolderToRecycledViewPool(holder, true); 7294 recycled = true; 7295 } 7296 } else { 7297 // NOTE: A view can fail to be recycled when it is scrolled off while an animation 7298 // runs. In this case, the item is eventually recycled by 7299 // ItemAnimatorRestoreListener#onAnimationFinished. 7300 7301 // TODO: consider cancelling an animation when an item is removed scrollBy, 7302 // to return it to the pool faster 7303 if (sVerboseLoggingEnabled) { 7304 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " 7305 + "re-visit here. We are still removing it from animation lists" 7306 + exceptionLabel()); 7307 } 7308 } 7309 // even if the holder is not removed, we still call this method so that it is removed 7310 // from view holder lists. 7311 mViewInfoStore.removeViewHolder(holder); 7312 if (!cached && !recycled && transientStatePreventsRecycling) { 7313 PoolingContainer.callPoolingContainerOnRelease(holder.itemView); 7314 holder.mBindingAdapter = null; 7315 holder.mOwnerRecyclerView = null; 7316 } 7317 } 7318 7319 /** 7320 * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. 7321 * 7322 * Pass false to dispatchRecycled for views that have not been bound. 7323 * 7324 * @param holder Holder to be added to the pool. 7325 * @param dispatchRecycled True to dispatch View recycled callbacks. 7326 */ addViewHolderToRecycledViewPool(@onNull ViewHolder holder, boolean dispatchRecycled)7327 void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { 7328 clearNestedRecyclerViewIfNotNested(holder); 7329 View itemView = holder.itemView; 7330 if (mAccessibilityDelegate != null) { 7331 AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate(); 7332 AccessibilityDelegateCompat originalDelegate = null; 7333 if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) { 7334 originalDelegate = 7335 ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate) 7336 .getAndRemoveOriginalDelegateForItem(itemView); 7337 } 7338 // Set the a11y delegate back to whatever the original delegate was. 7339 ViewCompat.setAccessibilityDelegate(itemView, originalDelegate); 7340 } 7341 if (dispatchRecycled) { 7342 dispatchViewRecycled(holder); 7343 } 7344 holder.mBindingAdapter = null; 7345 holder.mOwnerRecyclerView = null; 7346 getRecycledViewPool().putRecycledView(holder); 7347 } 7348 7349 /** 7350 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 7351 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 7352 * internal bookkeeping. 7353 */ quickRecycleScrapView(View view)7354 void quickRecycleScrapView(View view) { 7355 final ViewHolder holder = getChildViewHolderInt(view); 7356 holder.mScrapContainer = null; 7357 holder.mInChangeScrap = false; 7358 holder.clearReturnedFromScrapFlag(); 7359 recycleViewHolderInternal(holder); 7360 } 7361 7362 /** 7363 * Mark an attached view as scrap. 7364 * 7365 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 7366 * for rebinding and reuse. Requests for a view for a given position may return a 7367 * reused or rebound scrap view instance.</p> 7368 * 7369 * @param view View to scrap 7370 */ scrapView(View view)7371 void scrapView(View view) { 7372 final ViewHolder holder = getChildViewHolderInt(view); 7373 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) 7374 || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { 7375 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { 7376 throw new IllegalArgumentException("Called scrap view with an invalid view." 7377 + " Invalid views cannot be reused from scrap, they should rebound from" 7378 + " recycler pool." + exceptionLabel()); 7379 } 7380 holder.setScrapContainer(this, false); 7381 mAttachedScrap.add(holder); 7382 } else { 7383 if (mChangedScrap == null) { 7384 mChangedScrap = new ArrayList<ViewHolder>(); 7385 } 7386 holder.setScrapContainer(this, true); 7387 mChangedScrap.add(holder); 7388 } 7389 } 7390 7391 /** 7392 * Remove a previously scrapped view from the pool of eligible scrap. 7393 * 7394 * <p>This view will no longer be eligible for reuse until re-scrapped or 7395 * until it is explicitly removed and recycled.</p> 7396 */ unscrapView(ViewHolder holder)7397 void unscrapView(ViewHolder holder) { 7398 if (holder.mInChangeScrap) { 7399 mChangedScrap.remove(holder); 7400 } else { 7401 mAttachedScrap.remove(holder); 7402 } 7403 holder.mScrapContainer = null; 7404 holder.mInChangeScrap = false; 7405 holder.clearReturnedFromScrapFlag(); 7406 } 7407 getScrapCount()7408 int getScrapCount() { 7409 return mAttachedScrap.size(); 7410 } 7411 getScrapViewAt(int index)7412 View getScrapViewAt(int index) { 7413 return mAttachedScrap.get(index).itemView; 7414 } 7415 clearScrap()7416 void clearScrap() { 7417 mAttachedScrap.clear(); 7418 if (mChangedScrap != null) { 7419 mChangedScrap.clear(); 7420 } 7421 } 7422 getChangedScrapViewForPosition(int position)7423 ViewHolder getChangedScrapViewForPosition(int position) { 7424 // If pre-layout, check the changed scrap for an exact match. 7425 final int changedScrapSize; 7426 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { 7427 return null; 7428 } 7429 // find by position 7430 for (int i = 0; i < changedScrapSize; i++) { 7431 final ViewHolder holder = mChangedScrap.get(i); 7432 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { 7433 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 7434 return holder; 7435 } 7436 } 7437 // find by id 7438 if (mAdapter.hasStableIds()) { 7439 final int offsetPosition = mAdapterHelper.findPositionOffset(position); 7440 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { 7441 final long id = mAdapter.getItemId(offsetPosition); 7442 for (int i = 0; i < changedScrapSize; i++) { 7443 final ViewHolder holder = mChangedScrap.get(i); 7444 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { 7445 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 7446 return holder; 7447 } 7448 } 7449 } 7450 } 7451 return null; 7452 } 7453 7454 /** 7455 * Returns a view for the position either from attach scrap, hidden children, or cache. 7456 * 7457 * @param position Item position 7458 * @param dryRun Does a dry run, finds the ViewHolder but does not remove 7459 * @return a ViewHolder that can be re-used for this position. 7460 */ getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun)7461 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { 7462 final int scrapCount = mAttachedScrap.size(); 7463 7464 // Try first for an exact, non-invalid match from scrap. 7465 for (int i = 0; i < scrapCount; i++) { 7466 final ViewHolder holder = mAttachedScrap.get(i); 7467 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position 7468 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { 7469 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 7470 return holder; 7471 } 7472 } 7473 7474 if (!dryRun) { 7475 View view = mChildHelper.findHiddenNonRemovedView(position); 7476 if (view != null) { 7477 // This View is good to be used. We just need to unhide, detach and move to the 7478 // scrap list. 7479 final ViewHolder vh = getChildViewHolderInt(view); 7480 mChildHelper.unhide(view); 7481 int layoutIndex = mChildHelper.indexOfChild(view); 7482 if (layoutIndex == RecyclerView.NO_POSITION) { 7483 throw new IllegalStateException("layout index should not be -1 after " 7484 + "unhiding a view:" + vh + exceptionLabel()); 7485 } 7486 mChildHelper.detachViewFromParent(layoutIndex); 7487 scrapView(view); 7488 vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP 7489 | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); 7490 return vh; 7491 } 7492 } 7493 7494 // Search in our first-level recycled view cache. 7495 final int cacheSize = mCachedViews.size(); 7496 for (int i = 0; i < cacheSize; i++) { 7497 final ViewHolder holder = mCachedViews.get(i); 7498 // invalid view holders may be in cache if adapter has stable ids as they can be 7499 // retrieved via getScrapOrCachedViewForId 7500 if (!holder.isInvalid() && holder.getLayoutPosition() == position 7501 && !holder.isAttachedToTransitionOverlay()) { 7502 if (!dryRun) { 7503 mCachedViews.remove(i); 7504 } 7505 if (sVerboseLoggingEnabled) { 7506 Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position 7507 + ") found match in cache: " + holder); 7508 } 7509 return holder; 7510 } 7511 } 7512 return null; 7513 } 7514 getScrapOrCachedViewForId(long id, int type, boolean dryRun)7515 ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { 7516 // Look in our attached views first 7517 final int count = mAttachedScrap.size(); 7518 for (int i = count - 1; i >= 0; i--) { 7519 final ViewHolder holder = mAttachedScrap.get(i); 7520 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { 7521 if (type == holder.getItemViewType()) { 7522 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); 7523 if (holder.isRemoved()) { 7524 // this might be valid in two cases: 7525 // > item is removed but we are in pre-layout pass 7526 // >> do nothing. return as is. make sure we don't rebind 7527 // > item is removed then added to another position and we are in 7528 // post layout. 7529 // >> remove removed and invalid flags, add update flag to rebind 7530 // because item was invisible to us and we don't know what happened in 7531 // between. 7532 if (!mState.isPreLayout()) { 7533 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE 7534 | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); 7535 } 7536 } 7537 return holder; 7538 } else if (!dryRun) { 7539 // if we are running animations, it is actually better to keep it in scrap 7540 // but this would force layout manager to lay it out which would be bad. 7541 // Recycle this scrap. Type mismatch. 7542 mAttachedScrap.remove(i); 7543 removeDetachedView(holder.itemView, false); 7544 quickRecycleScrapView(holder.itemView); 7545 } 7546 } 7547 } 7548 7549 // Search the first-level cache 7550 final int cacheSize = mCachedViews.size(); 7551 for (int i = cacheSize - 1; i >= 0; i--) { 7552 final ViewHolder holder = mCachedViews.get(i); 7553 if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) { 7554 if (type == holder.getItemViewType()) { 7555 if (!dryRun) { 7556 mCachedViews.remove(i); 7557 } 7558 return holder; 7559 } else if (!dryRun) { 7560 recycleCachedViewAt(i); 7561 return null; 7562 } 7563 } 7564 } 7565 return null; 7566 } 7567 7568 @SuppressWarnings("unchecked") dispatchViewRecycled(@onNull ViewHolder holder)7569 void dispatchViewRecycled(@NonNull ViewHolder holder) { 7570 // TODO: Remove this once setRecyclerListener (currently deprecated) is deleted. 7571 if (mRecyclerListener != null) { 7572 mRecyclerListener.onViewRecycled(holder); 7573 } 7574 7575 final int listenerCount = mRecyclerListeners.size(); 7576 for (int i = 0; i < listenerCount; i++) { 7577 mRecyclerListeners.get(i).onViewRecycled(holder); 7578 } 7579 if (mAdapter != null) { 7580 mAdapter.onViewRecycled(holder); 7581 } 7582 if (mState != null) { 7583 mViewInfoStore.removeViewHolder(holder); 7584 } 7585 if (sVerboseLoggingEnabled) Log.d(TAG, "dispatchViewRecycled: " + holder); 7586 } 7587 onAdapterChanged(Adapter<?> oldAdapter, Adapter<?> newAdapter, boolean compatibleWithPrevious)7588 void onAdapterChanged(Adapter<?> oldAdapter, Adapter<?> newAdapter, 7589 boolean compatibleWithPrevious) { 7590 clear(); 7591 poolingContainerDetach(oldAdapter, true); 7592 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, 7593 compatibleWithPrevious); 7594 maybeSendPoolingContainerAttach(); 7595 } 7596 offsetPositionRecordsForMove(int from, int to)7597 void offsetPositionRecordsForMove(int from, int to) { 7598 final int start, end, inBetweenOffset; 7599 if (from < to) { 7600 start = from; 7601 end = to; 7602 inBetweenOffset = -1; 7603 } else { 7604 start = to; 7605 end = from; 7606 inBetweenOffset = 1; 7607 } 7608 final int cachedCount = mCachedViews.size(); 7609 for (int i = 0; i < cachedCount; i++) { 7610 final ViewHolder holder = mCachedViews.get(i); 7611 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 7612 continue; 7613 } 7614 if (holder.mPosition == from) { 7615 holder.offsetPosition(to - from, false); 7616 } else { 7617 holder.offsetPosition(inBetweenOffset, false); 7618 } 7619 if (sVerboseLoggingEnabled) { 7620 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " 7621 + holder); 7622 } 7623 } 7624 } 7625 offsetPositionRecordsForInsert(int insertedAt, int count)7626 void offsetPositionRecordsForInsert(int insertedAt, int count) { 7627 final int cachedCount = mCachedViews.size(); 7628 for (int i = 0; i < cachedCount; i++) { 7629 final ViewHolder holder = mCachedViews.get(i); 7630 if (holder != null && holder.mPosition >= insertedAt) { 7631 if (sVerboseLoggingEnabled) { 7632 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " 7633 + holder + " now at position " + (holder.mPosition + count)); 7634 } 7635 // insertions only affect post layout hence don't apply them to pre-layout. 7636 holder.offsetPosition(count, false); 7637 } 7638 } 7639 } 7640 7641 /** 7642 * @param removedFrom Remove start index 7643 * @param count Remove count 7644 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if 7645 * false, they'll be applied before the second layout pass 7646 */ offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout)7647 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { 7648 final int removedEnd = removedFrom + count; 7649 final int cachedCount = mCachedViews.size(); 7650 for (int i = cachedCount - 1; i >= 0; i--) { 7651 final ViewHolder holder = mCachedViews.get(i); 7652 if (holder != null) { 7653 if (holder.mPosition >= removedEnd) { 7654 if (sVerboseLoggingEnabled) { 7655 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i 7656 + " holder " + holder + " now at position " 7657 + (holder.mPosition - count)); 7658 } 7659 holder.offsetPosition(-count, applyToPreLayout); 7660 } else if (holder.mPosition >= removedFrom) { 7661 // Item for this view was removed. Dump it from the cache. 7662 holder.addFlags(ViewHolder.FLAG_REMOVED); 7663 recycleCachedViewAt(i); 7664 } 7665 } 7666 } 7667 } 7668 setViewCacheExtension(ViewCacheExtension extension)7669 void setViewCacheExtension(ViewCacheExtension extension) { 7670 mViewCacheExtension = extension; 7671 } 7672 setRecycledViewPool(RecycledViewPool pool)7673 void setRecycledViewPool(RecycledViewPool pool) { 7674 poolingContainerDetach(mAdapter); 7675 if (mRecyclerPool != null) { 7676 mRecyclerPool.detach(); 7677 } 7678 mRecyclerPool = pool; 7679 if (mRecyclerPool != null && getAdapter() != null) { 7680 mRecyclerPool.attach(); 7681 } 7682 maybeSendPoolingContainerAttach(); 7683 } 7684 maybeSendPoolingContainerAttach()7685 private void maybeSendPoolingContainerAttach() { 7686 if (mRecyclerPool != null 7687 && mAdapter != null 7688 && isAttachedToWindow()) { 7689 mRecyclerPool.attachForPoolingContainer(mAdapter); 7690 } 7691 } 7692 poolingContainerDetach(Adapter<?> adapter)7693 private void poolingContainerDetach(Adapter<?> adapter) { 7694 poolingContainerDetach(adapter, false); 7695 } 7696 poolingContainerDetach(Adapter<?> adapter, boolean isBeingReplaced)7697 private void poolingContainerDetach(Adapter<?> adapter, boolean isBeingReplaced) { 7698 if (mRecyclerPool != null) { 7699 mRecyclerPool.detachForPoolingContainer(adapter, isBeingReplaced); 7700 } 7701 } 7702 onAttachedToWindow()7703 void onAttachedToWindow() { 7704 maybeSendPoolingContainerAttach(); 7705 } 7706 onDetachedFromWindow()7707 void onDetachedFromWindow() { 7708 for (int i = 0; i < mCachedViews.size(); i++) { 7709 PoolingContainer.callPoolingContainerOnRelease(mCachedViews.get(i).itemView); 7710 } 7711 poolingContainerDetach(mAdapter); 7712 } 7713 getRecycledViewPool()7714 RecycledViewPool getRecycledViewPool() { 7715 if (mRecyclerPool == null) { 7716 mRecyclerPool = new RecycledViewPool(); 7717 maybeSendPoolingContainerAttach(); 7718 } 7719 return mRecyclerPool; 7720 } 7721 viewRangeUpdate(int positionStart, int itemCount)7722 void viewRangeUpdate(int positionStart, int itemCount) { 7723 final int positionEnd = positionStart + itemCount; 7724 final int cachedCount = mCachedViews.size(); 7725 for (int i = cachedCount - 1; i >= 0; i--) { 7726 final ViewHolder holder = mCachedViews.get(i); 7727 if (holder == null) { 7728 continue; 7729 } 7730 7731 final int pos = holder.mPosition; 7732 if (pos >= positionStart && pos < positionEnd) { 7733 holder.addFlags(ViewHolder.FLAG_UPDATE); 7734 recycleCachedViewAt(i); 7735 // cached views should not be flagged as changed because this will cause them 7736 // to animate when they are returned from cache. 7737 } 7738 } 7739 } 7740 markKnownViewsInvalid()7741 void markKnownViewsInvalid() { 7742 final int cachedCount = mCachedViews.size(); 7743 for (int i = 0; i < cachedCount; i++) { 7744 final ViewHolder holder = mCachedViews.get(i); 7745 if (holder != null) { 7746 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 7747 holder.addChangePayload(null); 7748 } 7749 } 7750 7751 if (mAdapter == null || !mAdapter.hasStableIds()) { 7752 // we cannot re-use cached views in this case. Recycle them all 7753 recycleAndClearCachedViews(); 7754 } 7755 } 7756 clearOldPositions()7757 void clearOldPositions() { 7758 final int cachedCount = mCachedViews.size(); 7759 for (int i = 0; i < cachedCount; i++) { 7760 final ViewHolder holder = mCachedViews.get(i); 7761 holder.clearOldPosition(); 7762 } 7763 final int scrapCount = mAttachedScrap.size(); 7764 for (int i = 0; i < scrapCount; i++) { 7765 mAttachedScrap.get(i).clearOldPosition(); 7766 } 7767 if (mChangedScrap != null) { 7768 final int changedScrapCount = mChangedScrap.size(); 7769 for (int i = 0; i < changedScrapCount; i++) { 7770 mChangedScrap.get(i).clearOldPosition(); 7771 } 7772 } 7773 } 7774 markItemDecorInsetsDirty()7775 void markItemDecorInsetsDirty() { 7776 final int cachedCount = mCachedViews.size(); 7777 for (int i = 0; i < cachedCount; i++) { 7778 final ViewHolder holder = mCachedViews.get(i); 7779 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); 7780 if (layoutParams != null) { 7781 layoutParams.mInsetsDirty = true; 7782 } 7783 } 7784 } 7785 } 7786 7787 /** 7788 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can 7789 * be controlled by the developer. 7790 * <p> 7791 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and 7792 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will 7793 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking 7794 * {@link RecycledViewPool}. 7795 * <p> 7796 * Note that, Recycler never sends Views to this method to be cached. It is developers 7797 * responsibility to decide whether they want to keep their Views in this custom cache or let 7798 * the default recycling policy handle it. 7799 */ 7800 public abstract static class ViewCacheExtension { 7801 7802 /** 7803 * Returns a View that can be binded to the given Adapter position. 7804 * <p> 7805 * This method should <b>not</b> create a new View. Instead, it is expected to return 7806 * an already created View that can be re-used for the given type and position. 7807 * If the View is marked as ignored, it should first call 7808 * {@link LayoutManager#stopIgnoringView(View)} before returning the View. 7809 * <p> 7810 * RecyclerView will re-bind the returned View to the position if necessary. 7811 * 7812 * @param recycler The Recycler that can be used to bind the View 7813 * @param position The adapter position 7814 * @param type The type of the View, defined by adapter 7815 * @return A View that is bound to the given position or NULL if there is no View to re-use 7816 * @see LayoutManager#ignoreView(View) 7817 */ getViewForPositionAndType(@onNull Recycler recycler, int position, int type)7818 public abstract @Nullable View getViewForPositionAndType(@NonNull Recycler recycler, 7819 int position, int type); 7820 } 7821 7822 /** 7823 * Base class for an Adapter 7824 * 7825 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 7826 * within a {@link RecyclerView}.</p> 7827 * 7828 * @param <VH> A class that extends ViewHolder that will be used by the adapter. 7829 */ 7830 public abstract static class Adapter<VH extends ViewHolder> { 7831 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 7832 private boolean mHasStableIds = false; 7833 private StateRestorationPolicy mStateRestorationPolicy = StateRestorationPolicy.ALLOW; 7834 7835 /** 7836 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent 7837 * an item. 7838 * <p> 7839 * This new ViewHolder should be constructed with a new View that can represent the items 7840 * of the given type. You can either create a new View manually or inflate it from an XML 7841 * layout file. 7842 * <p> 7843 * The new ViewHolder will be used to display items of the adapter using 7844 * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display 7845 * different items in the data set, it is a good idea to cache references to sub views of 7846 * the View to avoid unnecessary {@link View#findViewById(int)} calls. 7847 * 7848 * @param parent The ViewGroup into which the new View will be added after it is bound to 7849 * an adapter position. 7850 * @param viewType The view type of the new View. 7851 * @return A new ViewHolder that holds a View of the given view type. 7852 * @see #getItemViewType(int) 7853 * @see #onBindViewHolder(ViewHolder, int) 7854 */ onCreateViewHolder(@onNull ViewGroup parent, int viewType)7855 public abstract @NonNull VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); 7856 7857 /** 7858 * Called by RecyclerView to display the data at the specified position. This method should 7859 * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given 7860 * position. 7861 * <p> 7862 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 7863 * again if the position of the item changes in the data set unless the item itself is 7864 * invalidated or the new position cannot be determined. For this reason, you should only 7865 * use the <code>position</code> parameter while acquiring the related data item inside 7866 * this method and should not keep a copy of it. If you need the position of an item later 7867 * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which 7868 * will have the updated adapter position. 7869 * 7870 * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can 7871 * handle efficient partial bind. 7872 * 7873 * @param holder The ViewHolder which should be updated to represent the contents of the 7874 * item at the given position in the data set. 7875 * @param position The position of the item within the adapter's data set. 7876 */ onBindViewHolder(@onNull VH holder, int position)7877 public abstract void onBindViewHolder(@NonNull VH holder, int position); 7878 7879 /** 7880 * Called by RecyclerView to display the data at the specified position. This method 7881 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at 7882 * the given position. 7883 * <p> 7884 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method 7885 * again if the position of the item changes in the data set unless the item itself is 7886 * invalidated or the new position cannot be determined. For this reason, you should only 7887 * use the <code>position</code> parameter while acquiring the related data item inside 7888 * this method and should not keep a copy of it. If you need the position of an item later 7889 * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which 7890 * will have the updated adapter position. 7891 * <p> 7892 * Partial bind vs full bind: 7893 * <p> 7894 * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or 7895 * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, 7896 * the ViewHolder is currently bound to old data and Adapter may run an efficient partial 7897 * update using the payload info. If the payload is empty, Adapter must run a full bind. 7898 * Adapter should not assume that the payload passed in notify methods will be received by 7899 * onBindViewHolder(). For example when the view is not attached to the screen, the 7900 * payload in notifyItemChange() will be simply dropped. 7901 * 7902 * @param holder The ViewHolder which should be updated to represent the contents of the 7903 * item at the given position in the data set. 7904 * @param position The position of the item within the adapter's data set. 7905 * @param payloads A non-null list of merged payloads. Can be empty list if requires full 7906 * update. 7907 */ onBindViewHolder(@onNull VH holder, int position, @NonNull List<Object> payloads)7908 public void onBindViewHolder(@NonNull VH holder, int position, 7909 @NonNull List<Object> payloads) { 7910 onBindViewHolder(holder, position); 7911 } 7912 7913 /** 7914 * Returns the position of the given {@link ViewHolder} in the given {@link Adapter}. 7915 * 7916 * If the given {@link Adapter} is not part of this {@link Adapter}, 7917 * {@link RecyclerView#NO_POSITION} is returned. 7918 * 7919 * @param adapter The adapter which is a sub adapter of this adapter or itself. 7920 * @param viewHolder The ViewHolder whose local position in the given adapter will be 7921 * returned. 7922 * @param localPosition The position of the given {@link ViewHolder} in this 7923 * {@link Adapter}. 7924 * 7925 * @return The local position of the given {@link ViewHolder} in this {@link Adapter} 7926 * or {@link RecyclerView#NO_POSITION} if the {@link ViewHolder} is not bound to an item 7927 * or the given {@link Adapter} is not part of this Adapter (if this Adapter merges other 7928 * adapters). 7929 */ findRelativeAdapterPositionIn( @onNull Adapter<? extends ViewHolder> adapter, @NonNull ViewHolder viewHolder, int localPosition )7930 public int findRelativeAdapterPositionIn( 7931 @NonNull Adapter<? extends ViewHolder> adapter, 7932 @NonNull ViewHolder viewHolder, 7933 int localPosition 7934 ) { 7935 if (adapter == this) { 7936 return localPosition; 7937 } 7938 return NO_POSITION; 7939 } 7940 7941 /** 7942 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new 7943 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. 7944 * 7945 * @see #onCreateViewHolder(ViewGroup, int) 7946 */ createViewHolder(@onNull ViewGroup parent, int viewType)7947 public final @NonNull VH createViewHolder(@NonNull ViewGroup parent, int viewType) { 7948 try { 7949 if (TraceCompat.isEnabled()) { 7950 Trace.beginSection(String.format("RV onCreateViewHolder type=0x%X", viewType)); 7951 } 7952 final VH holder = onCreateViewHolder(parent, viewType); 7953 if (holder.itemView.getParent() != null) { 7954 throw new IllegalStateException("ViewHolder views must not be attached when" 7955 + " created. Ensure that you are not passing 'true' to the attachToRoot" 7956 + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); 7957 } 7958 holder.mItemViewType = viewType; 7959 return holder; 7960 } finally { 7961 Trace.endSection(); 7962 } 7963 } 7964 7965 /** 7966 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the 7967 * {@link ViewHolder} contents with the item at the given position and also sets up some 7968 * private fields to be used by RecyclerView. 7969 * 7970 * Adapters that merge other adapters should use 7971 * {@link #bindViewHolder(ViewHolder, int)} when calling nested adapters so that 7972 * RecyclerView can track which adapter bound the {@link ViewHolder} to return the correct 7973 * position from {@link ViewHolder#getBindingAdapterPosition()} method. 7974 * They should also override 7975 * the {@link #findRelativeAdapterPositionIn(Adapter, ViewHolder, int)} method. 7976 * 7977 * @param holder The view holder whose contents should be updated 7978 * @param position The position of the holder with respect to this adapter 7979 * @see #onBindViewHolder(ViewHolder, int) 7980 */ bindViewHolder(@onNull VH holder, int position)7981 public final void bindViewHolder(@NonNull VH holder, int position) { 7982 boolean rootBind = holder.mBindingAdapter == null; 7983 if (rootBind) { 7984 holder.mPosition = position; 7985 if (hasStableIds()) { 7986 holder.mItemId = getItemId(position); 7987 } 7988 holder.setFlags(ViewHolder.FLAG_BOUND, 7989 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID 7990 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); 7991 if (TraceCompat.isEnabled()) { 7992 // Note: we only trace when rootBind=true to avoid duplicate trace sections 7993 Trace.beginSection( 7994 String.format("RV onBindViewHolder type=0x%X", holder.mItemViewType) 7995 ); 7996 } 7997 } 7998 holder.mBindingAdapter = this; 7999 if (sDebugAssertionsEnabled) { 8000 if (holder.itemView.getParent() == null 8001 && (holder.itemView.isAttachedToWindow() 8002 != holder.isTmpDetached())) { 8003 throw new IllegalStateException("Temp-detached state out of sync with reality. " 8004 + "holder.isTmpDetached(): " + holder.isTmpDetached() 8005 + ", attached to window: " 8006 + holder.itemView.isAttachedToWindow() 8007 + ", holder: " + holder); 8008 } 8009 if (holder.itemView.getParent() == null 8010 && holder.itemView.isAttachedToWindow()) { 8011 throw new IllegalStateException( 8012 "Attempting to bind attached holder with no parent" 8013 + " (AKA temp detached): " + holder); 8014 } 8015 } 8016 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); 8017 if (rootBind) { 8018 holder.clearPayload(); 8019 final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); 8020 if (layoutParams instanceof RecyclerView.LayoutParams) { 8021 ((LayoutParams) layoutParams).mInsetsDirty = true; 8022 } 8023 Trace.endSection(); 8024 } 8025 } 8026 8027 /** 8028 * Return the view type of the item at <code>position</code> for the purposes 8029 * of view recycling. 8030 * 8031 * <p>The default implementation of this method returns 0, making the assumption of 8032 * a single view type for the adapter. Unlike ListView adapters, types need not 8033 * be contiguous. Consider using id resources to uniquely identify item view types. 8034 * 8035 * @param position position to query 8036 * @return integer value identifying the type of the view needed to represent the item at 8037 * <code>position</code>. Type codes need not be contiguous. 8038 */ getItemViewType(int position)8039 public int getItemViewType(int position) { 8040 return 0; 8041 } 8042 8043 /** 8044 * Indicates whether each item in the data set can be represented with a unique identifier 8045 * of type {@link java.lang.Long}. 8046 * 8047 * @param hasStableIds Whether items in data set have unique identifiers or not. 8048 * @see #hasStableIds() 8049 * @see #getItemId(int) 8050 */ setHasStableIds(boolean hasStableIds)8051 public void setHasStableIds(boolean hasStableIds) { 8052 if (hasObservers()) { 8053 throw new IllegalStateException("Cannot change whether this adapter has " 8054 + "stable IDs while the adapter has registered observers."); 8055 } 8056 mHasStableIds = hasStableIds; 8057 } 8058 8059 /** 8060 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 8061 * would return false this method should return {@link #NO_ID}. The default implementation 8062 * of this method returns {@link #NO_ID}. 8063 * 8064 * @param position Adapter position to query 8065 * @return the stable ID of the item at position 8066 */ getItemId(int position)8067 public long getItemId(int position) { 8068 return NO_ID; 8069 } 8070 8071 /** 8072 * Returns the total number of items in the data set held by the adapter. 8073 * 8074 * @return The total number of items in this adapter. 8075 */ getItemCount()8076 public abstract int getItemCount(); 8077 8078 /** 8079 * Returns true if this adapter publishes a unique <code>long</code> value that can 8080 * act as a key for the item at a given position in the data set. If that item is relocated 8081 * in the data set, the ID returned for that item should be the same. 8082 * 8083 * @return true if this adapter's items have stable IDs 8084 */ hasStableIds()8085 public final boolean hasStableIds() { 8086 return mHasStableIds; 8087 } 8088 8089 /** 8090 * Called when a view created by this adapter has been recycled. 8091 * 8092 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 8093 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 8094 * fallen out of visibility or a set of cached views represented by views still 8095 * attached to the parent RecyclerView. If an item view has large or expensive data 8096 * bound to it such as large bitmaps, this may be a good place to release those 8097 * resources.</p> 8098 * <p> 8099 * RecyclerView calls this method right before clearing ViewHolder's internal data and 8100 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 8101 * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get 8102 * its adapter position. 8103 * 8104 * @param holder The ViewHolder for the view being recycled 8105 */ onViewRecycled(@onNull VH holder)8106 public void onViewRecycled(@NonNull VH holder) { 8107 } 8108 8109 /** 8110 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled 8111 * due to its transient state. Upon receiving this callback, Adapter can clear the 8112 * animation(s) that effect the View's transient state and return <code>true</code> so that 8113 * the View can be recycled. Keep in mind that the View in question is already removed from 8114 * the RecyclerView. 8115 * <p> 8116 * In some cases, it is acceptable to recycle a View although it has transient state. Most 8117 * of the time, this is a case where the transient state will be cleared in 8118 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. 8119 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return 8120 * value of this method to decide whether the View should be recycled or not. 8121 * <p> 8122 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you 8123 * should never receive this callback because RecyclerView keeps those Views as children 8124 * until their animations are complete. This callback is useful when children of the item 8125 * views create animations which may not be easy to implement using an {@link ItemAnimator}. 8126 * <p> 8127 * You should <em>never</em> fix this issue by calling 8128 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called 8129 * <code>holder.itemView.setHasTransientState(true);</code>. Each 8130 * <code>View.setHasTransientState(true)</code> call must be matched by a 8131 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View 8132 * may become inconsistent. You should always prefer to end or cancel animations that are 8133 * triggering the transient state instead of handling it manually. 8134 * 8135 * @param holder The ViewHolder containing the View that could not be recycled due to its 8136 * transient state. 8137 * @return True if the View should be recycled, false otherwise. Note that if this method 8138 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of 8139 * the View and recycle it regardless. If this method returns <code>false</code>, 8140 * RecyclerView will check the View's transient state again before giving a final decision. 8141 * Default implementation returns false. 8142 */ onFailedToRecycleView(@onNull VH holder)8143 public boolean onFailedToRecycleView(@NonNull VH holder) { 8144 return false; 8145 } 8146 8147 /** 8148 * Called when a view created by this adapter has been attached to a window. 8149 * 8150 * <p>This can be used as a reasonable signal that the view is about to be seen 8151 * by the user. If the adapter previously freed any resources in 8152 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 8153 * those resources should be restored here.</p> 8154 * 8155 * @param holder Holder of the view being attached 8156 */ onViewAttachedToWindow(@onNull VH holder)8157 public void onViewAttachedToWindow(@NonNull VH holder) { 8158 } 8159 8160 /** 8161 * Called when a view created by this adapter has been detached from its window. 8162 * 8163 * <p>Becoming detached from the window is not necessarily a permanent condition; 8164 * the consumer of an Adapter's views may choose to cache views offscreen while they 8165 * are not visible, attaching and detaching them as appropriate.</p> 8166 * 8167 * @param holder Holder of the view being detached 8168 */ onViewDetachedFromWindow(@onNull VH holder)8169 public void onViewDetachedFromWindow(@NonNull VH holder) { 8170 } 8171 8172 /** 8173 * Returns true if one or more observers are attached to this adapter. 8174 * 8175 * @return true if this adapter has observers 8176 */ hasObservers()8177 public final boolean hasObservers() { 8178 return mObservable.hasObservers(); 8179 } 8180 8181 /** 8182 * Register a new observer to listen for data changes. 8183 * 8184 * <p>The adapter may publish a variety of events describing specific changes. 8185 * Not all adapters may support all change types and some may fall back to a generic 8186 * {@link RecyclerView.AdapterDataObserver#onChanged() 8187 * "something changed"} event if more specific data is not available.</p> 8188 * 8189 * <p>Components registering observers with an adapter are responsible for 8190 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 8191 * unregistering} those observers when finished.</p> 8192 * 8193 * @param observer Observer to register 8194 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) 8195 */ registerAdapterDataObserver(@onNull AdapterDataObserver observer)8196 public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { 8197 mObservable.registerObserver(observer); 8198 } 8199 8200 /** 8201 * Unregister an observer currently listening for data changes. 8202 * 8203 * <p>The unregistered observer will no longer receive events about changes 8204 * to the adapter.</p> 8205 * 8206 * @param observer Observer to unregister 8207 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) 8208 */ unregisterAdapterDataObserver(@onNull AdapterDataObserver observer)8209 public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { 8210 mObservable.unregisterObserver(observer); 8211 } 8212 8213 /** 8214 * Called by RecyclerView when it starts observing this Adapter. 8215 * <p> 8216 * Keep in mind that same adapter may be observed by multiple RecyclerViews. 8217 * 8218 * @param recyclerView The RecyclerView instance which started observing this adapter. 8219 * @see #onDetachedFromRecyclerView(RecyclerView) 8220 */ onAttachedToRecyclerView(@onNull RecyclerView recyclerView)8221 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 8222 } 8223 8224 /** 8225 * Called by RecyclerView when it stops observing this Adapter. 8226 * 8227 * @param recyclerView The RecyclerView instance which stopped observing this adapter. 8228 * @see #onAttachedToRecyclerView(RecyclerView) 8229 */ onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)8230 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 8231 } 8232 8233 /** 8234 * Notify any registered observers that the data set has changed. 8235 * 8236 * <p>There are two different classes of data change events, item changes and structural 8237 * changes. Item changes are when a single item has its data updated but no positional 8238 * changes have occurred. Structural changes are when items are inserted, removed or moved 8239 * within the data set.</p> 8240 * 8241 * <p>This event does not specify what about the data set has changed, forcing 8242 * any observers to assume that all existing items and structure may no longer be valid. 8243 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 8244 * 8245 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 8246 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 8247 * this method is used. This can help for the purposes of animation and visual 8248 * object persistence but individual item views will still need to be rebound 8249 * and relaid out.</p> 8250 * 8251 * <p>If you are writing an adapter it will always be more efficient to use the more 8252 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 8253 * as a last resort.</p> 8254 * 8255 * @see #notifyItemChanged(int) 8256 * @see #notifyItemInserted(int) 8257 * @see #notifyItemRemoved(int) 8258 * @see #notifyItemRangeChanged(int, int) 8259 * @see #notifyItemRangeInserted(int, int) 8260 * @see #notifyItemRangeRemoved(int, int) 8261 */ notifyDataSetChanged()8262 public final void notifyDataSetChanged() { 8263 mObservable.notifyChanged(); 8264 } 8265 8266 /** 8267 * Notify any registered observers that the item at <code>position</code> has changed. 8268 * Equivalent to calling <code>notifyItemChanged(position, null);</code>. 8269 * 8270 * <p>This is an item change event, not a structural change event. It indicates that any 8271 * reflection of the data at <code>position</code> is out of date and should be updated. 8272 * The item at <code>position</code> retains the same identity.</p> 8273 * 8274 * @param position Position of the item that has changed 8275 * @see #notifyItemRangeChanged(int, int) 8276 */ notifyItemChanged(int position)8277 public final void notifyItemChanged(int position) { 8278 mObservable.notifyItemRangeChanged(position, 1); 8279 } 8280 8281 /** 8282 * Notify any registered observers that the item at <code>position</code> has changed with 8283 * an optional payload object. 8284 * 8285 * <p>This is an item change event, not a structural change event. It indicates that any 8286 * reflection of the data at <code>position</code> is out of date and should be updated. 8287 * The item at <code>position</code> retains the same identity. 8288 * </p> 8289 * 8290 * <p> 8291 * Client can optionally pass a payload for partial change. These payloads will be merged 8292 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 8293 * item is already represented by a ViewHolder and it will be rebound to the same 8294 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 8295 * payloads on that item and prevent future payload until 8296 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 8297 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 8298 * attached, the payload will be simply dropped. 8299 * 8300 * @param position Position of the item that has changed 8301 * @param payload Optional parameter, use null to identify a "full" update 8302 * @see #notifyItemRangeChanged(int, int) 8303 */ notifyItemChanged(int position, @Nullable Object payload)8304 public final void notifyItemChanged(int position, @Nullable Object payload) { 8305 mObservable.notifyItemRangeChanged(position, 1, payload); 8306 } 8307 8308 /** 8309 * Notify any registered observers that the <code>itemCount</code> items starting at 8310 * position <code>positionStart</code> have changed. 8311 * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>. 8312 * 8313 * <p>This is an item change event, not a structural change event. It indicates that 8314 * any reflection of the data in the given position range is out of date and should 8315 * be updated. The items in the given range retain the same identity.</p> 8316 * 8317 * @param positionStart Position of the first item that has changed 8318 * @param itemCount Number of items that have changed 8319 * @see #notifyItemChanged(int) 8320 */ notifyItemRangeChanged(int positionStart, int itemCount)8321 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 8322 mObservable.notifyItemRangeChanged(positionStart, itemCount); 8323 } 8324 8325 /** 8326 * Notify any registered observers that the <code>itemCount</code> items starting at 8327 * position <code>positionStart</code> have changed. An optional payload can be 8328 * passed to each changed item. 8329 * 8330 * <p>This is an item change event, not a structural change event. It indicates that any 8331 * reflection of the data in the given position range is out of date and should be updated. 8332 * The items in the given range retain the same identity. 8333 * </p> 8334 * 8335 * <p> 8336 * Client can optionally pass a payload for partial change. These payloads will be merged 8337 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 8338 * item is already represented by a ViewHolder and it will be rebound to the same 8339 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 8340 * payloads on that item and prevent future payload until 8341 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 8342 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 8343 * attached, the payload will be simply dropped. 8344 * 8345 * @param positionStart Position of the first item that has changed 8346 * @param itemCount Number of items that have changed 8347 * @param payload Optional parameter, use null to identify a "full" update 8348 * @see #notifyItemChanged(int) 8349 */ notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload)8350 public final void notifyItemRangeChanged(int positionStart, int itemCount, 8351 @Nullable Object payload) { 8352 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); 8353 } 8354 8355 /** 8356 * Notify any registered observers that the item reflected at <code>position</code> 8357 * has been newly inserted. The item previously at <code>position</code> is now at 8358 * position <code>position + 1</code>. 8359 * 8360 * <p>This is a structural change event. Representations of other existing items in the 8361 * data set are still considered up to date and will not be rebound, though their 8362 * positions may be altered.</p> 8363 * 8364 * @param position Position of the newly inserted item in the data set 8365 * @see #notifyItemRangeInserted(int, int) 8366 */ notifyItemInserted(int position)8367 public final void notifyItemInserted(int position) { 8368 mObservable.notifyItemRangeInserted(position, 1); 8369 } 8370 8371 /** 8372 * Notify any registered observers that the item reflected at <code>fromPosition</code> 8373 * has been moved to <code>toPosition</code>. 8374 * 8375 * <p>This is a structural change event. Representations of other existing items in the 8376 * data set are still considered up to date and will not be rebound, though their 8377 * positions may be altered.</p> 8378 * 8379 * @param fromPosition Previous position of the item. 8380 * @param toPosition New position of the item. 8381 */ notifyItemMoved(int fromPosition, int toPosition)8382 public final void notifyItemMoved(int fromPosition, int toPosition) { 8383 mObservable.notifyItemMoved(fromPosition, toPosition); 8384 } 8385 8386 /** 8387 * Notify any registered observers that the currently reflected <code>itemCount</code> 8388 * items starting at <code>positionStart</code> have been newly inserted. The items 8389 * previously located at <code>positionStart</code> and beyond can now be found starting 8390 * at position <code>positionStart + itemCount</code>. 8391 * 8392 * <p>This is a structural change event. Representations of other existing items in the 8393 * data set are still considered up to date and will not be rebound, though their positions 8394 * may be altered.</p> 8395 * 8396 * @param positionStart Position of the first item that was inserted 8397 * @param itemCount Number of items inserted 8398 * @see #notifyItemInserted(int) 8399 */ notifyItemRangeInserted(int positionStart, int itemCount)8400 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 8401 mObservable.notifyItemRangeInserted(positionStart, itemCount); 8402 } 8403 8404 /** 8405 * Notify any registered observers that the item previously located at <code>position</code> 8406 * has been removed from the data set. The items previously located at and after 8407 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 8408 * 8409 * <p>This is a structural change event. Representations of other existing items in the 8410 * data set are still considered up to date and will not be rebound, though their positions 8411 * may be altered.</p> 8412 * 8413 * @param position Position of the item that has now been removed 8414 * @see #notifyItemRangeRemoved(int, int) 8415 */ notifyItemRemoved(int position)8416 public final void notifyItemRemoved(int position) { 8417 mObservable.notifyItemRangeRemoved(position, 1); 8418 } 8419 8420 /** 8421 * Notify any registered observers that the <code>itemCount</code> items previously 8422 * located at <code>positionStart</code> have been removed from the data set. The items 8423 * previously located at and after <code>positionStart + itemCount</code> may now be found 8424 * at <code>oldPosition - itemCount</code>. 8425 * 8426 * <p>This is a structural change event. Representations of other existing items in the data 8427 * set are still considered up to date and will not be rebound, though their positions 8428 * may be altered.</p> 8429 * 8430 * @param positionStart Previous position of the first item that was removed 8431 * @param itemCount Number of items removed from the data set 8432 */ notifyItemRangeRemoved(int positionStart, int itemCount)8433 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 8434 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 8435 } 8436 8437 /** 8438 * Sets the state restoration strategy for the Adapter. 8439 * 8440 * By default, it is set to {@link StateRestorationPolicy#ALLOW} which means RecyclerView 8441 * expects any set Adapter to be immediately capable of restoring the RecyclerView's saved 8442 * scroll position. 8443 * <p> 8444 * This behaviour might be undesired if the Adapter's data is loaded asynchronously, and 8445 * thus unavailable during initial layout (e.g. after Activity rotation). To avoid losing 8446 * scroll position, you can change this to be either 8447 * {@link StateRestorationPolicy#PREVENT_WHEN_EMPTY} or 8448 * {@link StateRestorationPolicy#PREVENT}. 8449 * Note that the former means your RecyclerView will restore state as soon as Adapter has 8450 * 1 or more items while the latter requires you to call 8451 * {@link #setStateRestorationPolicy(StateRestorationPolicy)} with either 8452 * {@link StateRestorationPolicy#ALLOW} or 8453 * {@link StateRestorationPolicy#PREVENT_WHEN_EMPTY} again when the Adapter is 8454 * ready to restore its state. 8455 * <p> 8456 * RecyclerView will still layout even when State restoration is disabled. The behavior of 8457 * how State is restored is up to the {@link LayoutManager}. All default LayoutManagers 8458 * will override current state with restored state when state restoration happens (unless 8459 * an explicit call to {@link LayoutManager#scrollToPosition(int)} is made). 8460 * <p> 8461 * Calling this method after state is restored will not have any effect other than changing 8462 * the return value of {@link #getStateRestorationPolicy()}. 8463 * 8464 * @param strategy The saved state restoration strategy for this Adapter. 8465 * @see #getStateRestorationPolicy() 8466 */ setStateRestorationPolicy(@onNull StateRestorationPolicy strategy)8467 public void setStateRestorationPolicy(@NonNull StateRestorationPolicy strategy) { 8468 mStateRestorationPolicy = strategy; 8469 mObservable.notifyStateRestorationPolicyChanged(); 8470 } 8471 8472 /** 8473 * Returns when this Adapter wants to restore the state. 8474 * 8475 * @return The current {@link StateRestorationPolicy} for this Adapter. Defaults to 8476 * {@link StateRestorationPolicy#ALLOW}. 8477 * @see #setStateRestorationPolicy(StateRestorationPolicy) 8478 */ getStateRestorationPolicy()8479 public final @NonNull StateRestorationPolicy getStateRestorationPolicy() { 8480 return mStateRestorationPolicy; 8481 } 8482 8483 /** 8484 * Called by the RecyclerView to decide whether the SavedState should be given to the 8485 * LayoutManager or not. 8486 * 8487 * @return {@code true} if the Adapter is ready to restore its state, {@code false} 8488 * otherwise. 8489 */ canRestoreState()8490 boolean canRestoreState() { 8491 switch (mStateRestorationPolicy) { 8492 case PREVENT: 8493 return false; 8494 case PREVENT_WHEN_EMPTY: 8495 return getItemCount() > 0; 8496 default: 8497 return true; 8498 } 8499 } 8500 8501 /** 8502 * Defines how this Adapter wants to restore its state after a view reconstruction (e.g. 8503 * configuration change). 8504 */ 8505 public enum StateRestorationPolicy { 8506 /** 8507 * Adapter is ready to restore State immediately, RecyclerView will provide the state 8508 * to the LayoutManager in the next layout pass. 8509 */ 8510 ALLOW, 8511 /** 8512 * Adapter is ready to restore State when it has more than 0 items. RecyclerView will 8513 * provide the state to the LayoutManager as soon as the Adapter has 1 or more items. 8514 */ 8515 PREVENT_WHEN_EMPTY, 8516 /** 8517 * RecyclerView will not restore the state for the Adapter until a call to 8518 * {@link #setStateRestorationPolicy(StateRestorationPolicy)} is made with either 8519 * {@link #ALLOW} or {@link #PREVENT_WHEN_EMPTY}. 8520 */ 8521 PREVENT 8522 } 8523 } 8524 8525 @SuppressWarnings("unchecked") dispatchChildDetached(View child)8526 void dispatchChildDetached(View child) { 8527 final ViewHolder viewHolder = getChildViewHolderInt(child); 8528 onChildDetachedFromWindow(child); 8529 if (mAdapter != null && viewHolder != null) { 8530 mAdapter.onViewDetachedFromWindow(viewHolder); 8531 } 8532 if (mOnChildAttachStateListeners != null) { 8533 final int cnt = mOnChildAttachStateListeners.size(); 8534 for (int i = cnt - 1; i >= 0; i--) { 8535 mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child); 8536 } 8537 } 8538 } 8539 8540 @SuppressWarnings("unchecked") dispatchChildAttached(View child)8541 void dispatchChildAttached(View child) { 8542 final ViewHolder viewHolder = getChildViewHolderInt(child); 8543 onChildAttachedToWindow(child); 8544 if (mAdapter != null && viewHolder != null) { 8545 mAdapter.onViewAttachedToWindow(viewHolder); 8546 } 8547 if (mOnChildAttachStateListeners != null) { 8548 final int cnt = mOnChildAttachStateListeners.size(); 8549 for (int i = cnt - 1; i >= 0; i--) { 8550 mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); 8551 } 8552 } 8553 } 8554 8555 /** 8556 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 8557 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 8558 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 8559 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 8560 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 8561 * layout managers are provided for general use. 8562 * <p/> 8563 * If the LayoutManager specifies a default constructor or one with the signature 8564 * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will 8565 * instantiate and set the LayoutManager when being inflated. Most used properties can 8566 * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case 8567 * a LayoutManager specifies both constructors, the non-default constructor will take 8568 * precedence. 8569 */ 8570 public abstract static class LayoutManager { 8571 ChildHelper mChildHelper; 8572 RecyclerView mRecyclerView; 8573 8574 /** 8575 * The callback used for retrieving information about a RecyclerView and its children in the 8576 * horizontal direction. 8577 */ 8578 private final ViewBoundsCheck.Callback mHorizontalBoundCheckCallback = 8579 new ViewBoundsCheck.Callback() { 8580 @Override 8581 public View getChildAt(int index) { 8582 return LayoutManager.this.getChildAt(index); 8583 } 8584 8585 @Override 8586 public int getParentStart() { 8587 return LayoutManager.this.getPaddingLeft(); 8588 } 8589 8590 @Override 8591 public int getParentEnd() { 8592 return LayoutManager.this.getWidth() - LayoutManager.this.getPaddingRight(); 8593 } 8594 8595 @Override 8596 public int getChildStart(View view) { 8597 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 8598 view.getLayoutParams(); 8599 return LayoutManager.this.getDecoratedLeft(view) - params.leftMargin; 8600 } 8601 8602 @Override 8603 public int getChildEnd(View view) { 8604 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 8605 view.getLayoutParams(); 8606 return LayoutManager.this.getDecoratedRight(view) + params.rightMargin; 8607 } 8608 }; 8609 8610 /** 8611 * The callback used for retrieving information about a RecyclerView and its children in the 8612 * vertical direction. 8613 */ 8614 private final ViewBoundsCheck.Callback mVerticalBoundCheckCallback = 8615 new ViewBoundsCheck.Callback() { 8616 @Override 8617 public View getChildAt(int index) { 8618 return LayoutManager.this.getChildAt(index); 8619 } 8620 8621 @Override 8622 public int getParentStart() { 8623 return LayoutManager.this.getPaddingTop(); 8624 } 8625 8626 @Override 8627 public int getParentEnd() { 8628 return LayoutManager.this.getHeight() 8629 - LayoutManager.this.getPaddingBottom(); 8630 } 8631 8632 @Override 8633 public int getChildStart(View view) { 8634 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 8635 view.getLayoutParams(); 8636 return LayoutManager.this.getDecoratedTop(view) - params.topMargin; 8637 } 8638 8639 @Override 8640 public int getChildEnd(View view) { 8641 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 8642 view.getLayoutParams(); 8643 return LayoutManager.this.getDecoratedBottom(view) + params.bottomMargin; 8644 } 8645 }; 8646 8647 /** 8648 * Utility objects used to check the boundaries of children against their parent 8649 * RecyclerView. 8650 * 8651 * @see #isViewPartiallyVisible(View, boolean, boolean), 8652 * {@link LinearLayoutManager#findOneVisibleChild(int, int, boolean, boolean)}, 8653 * and {@link LinearLayoutManager#findOnePartiallyOrCompletelyInvisibleChild(int, int)}. 8654 */ 8655 ViewBoundsCheck mHorizontalBoundCheck = new ViewBoundsCheck(mHorizontalBoundCheckCallback); 8656 ViewBoundsCheck mVerticalBoundCheck = new ViewBoundsCheck(mVerticalBoundCheckCallback); 8657 8658 @Nullable SmoothScroller mSmoothScroller; 8659 8660 boolean mRequestedSimpleAnimations = false; 8661 8662 boolean mIsAttachedToWindow = false; 8663 8664 /** 8665 * This field is only set via the deprecated {@link #setAutoMeasureEnabled(boolean)} and is 8666 * only accessed via {@link #isAutoMeasureEnabled()} for backwards compatability reasons. 8667 */ 8668 boolean mAutoMeasure = false; 8669 8670 /** 8671 * LayoutManager has its own more strict measurement cache to avoid re-measuring a child 8672 * if the space that will be given to it is already larger than what it has measured before. 8673 */ 8674 private boolean mMeasurementCacheEnabled = true; 8675 8676 private boolean mItemPrefetchEnabled = true; 8677 8678 /** 8679 * Written by {@link GapWorker} when prefetches occur to track largest number of view ever 8680 * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or 8681 * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. 8682 * 8683 * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, 8684 * will be reset upon layout to prevent initial prefetches (often large, since they're 8685 * proportional to expected child count) from expanding cache permanently. 8686 */ 8687 int mPrefetchMaxCountObserved; 8688 8689 /** 8690 * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. 8691 */ 8692 boolean mPrefetchMaxObservedInInitialPrefetch; 8693 8694 /** 8695 * These measure specs might be the measure specs that were passed into RecyclerView's 8696 * onMeasure method OR fake measure specs created by the RecyclerView. 8697 * For example, when a layout is run, RecyclerView always sets these specs to be 8698 * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. 8699 * <p> 8700 * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the 8701 * API level and sets the size to 0 pre-M to avoid any issue that might be caused by 8702 * corrupt values. Older platforms have no responsibility to provide a size if they set 8703 * mode to unspecified. 8704 */ 8705 private int mWidthMode, mHeightMode; 8706 private int mWidth, mHeight; 8707 8708 8709 /** 8710 * Interface for LayoutManagers to request items to be prefetched, based on position, with 8711 * specified distance from viewport, which indicates priority. 8712 * 8713 * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 8714 * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 8715 */ 8716 public interface LayoutPrefetchRegistry { 8717 /** 8718 * Requests an an item to be prefetched, based on position, with a specified distance, 8719 * indicating priority. 8720 * 8721 * @param layoutPosition Position of the item to prefetch. 8722 * @param pixelDistance Distance from the current viewport to the bounds of the item, 8723 * must be non-negative. 8724 */ addPosition(int layoutPosition, int pixelDistance)8725 void addPosition(int layoutPosition, int pixelDistance); 8726 } 8727 setRecyclerView(RecyclerView recyclerView)8728 void setRecyclerView(RecyclerView recyclerView) { 8729 if (recyclerView == null) { 8730 mRecyclerView = null; 8731 mChildHelper = null; 8732 mWidth = 0; 8733 mHeight = 0; 8734 } else { 8735 mRecyclerView = recyclerView; 8736 mChildHelper = recyclerView.mChildHelper; 8737 mWidth = recyclerView.getWidth(); 8738 mHeight = recyclerView.getHeight(); 8739 } 8740 mWidthMode = MeasureSpec.EXACTLY; 8741 mHeightMode = MeasureSpec.EXACTLY; 8742 } 8743 setMeasureSpecs(int wSpec, int hSpec)8744 void setMeasureSpecs(int wSpec, int hSpec) { 8745 mWidth = MeasureSpec.getSize(wSpec); 8746 mWidthMode = MeasureSpec.getMode(wSpec); 8747 if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 8748 mWidth = 0; 8749 } 8750 8751 mHeight = MeasureSpec.getSize(hSpec); 8752 mHeightMode = MeasureSpec.getMode(hSpec); 8753 if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { 8754 mHeight = 0; 8755 } 8756 } 8757 8758 /** 8759 * Called after a layout is calculated during a measure pass when using auto-measure. 8760 * <p> 8761 * It simply traverses all children to calculate a bounding box then calls 8762 * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method 8763 * if they need to handle the bounding box differently. 8764 * <p> 8765 * For example, GridLayoutManager override that method to ensure that even if a column is 8766 * empty, the GridLayoutManager still measures wide enough to include it. 8767 * 8768 * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure 8769 * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure 8770 */ setMeasuredDimensionFromChildren(int widthSpec, int heightSpec)8771 void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { 8772 final int count = getChildCount(); 8773 if (count == 0) { 8774 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 8775 return; 8776 } 8777 int minX = Integer.MAX_VALUE; 8778 int minY = Integer.MAX_VALUE; 8779 int maxX = Integer.MIN_VALUE; 8780 int maxY = Integer.MIN_VALUE; 8781 8782 for (int i = 0; i < count; i++) { 8783 View child = getChildAt(i); 8784 final Rect bounds = mRecyclerView.mTempRect; 8785 getDecoratedBoundsWithMargins(child, bounds); 8786 if (bounds.left < minX) { 8787 minX = bounds.left; 8788 } 8789 if (bounds.right > maxX) { 8790 maxX = bounds.right; 8791 } 8792 if (bounds.top < minY) { 8793 minY = bounds.top; 8794 } 8795 if (bounds.bottom > maxY) { 8796 maxY = bounds.bottom; 8797 } 8798 } 8799 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); 8800 setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); 8801 } 8802 8803 /** 8804 * Sets the measured dimensions from the given bounding box of the children and the 8805 * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is 8806 * only called if a LayoutManager returns <code>true</code> from 8807 * {@link #isAutoMeasureEnabled()} and it is called after the RecyclerView calls 8808 * {@link LayoutManager#onLayoutChildren(Recycler, State)} in the execution of 8809 * {@link RecyclerView#onMeasure(int, int)}. 8810 * <p> 8811 * This method must call {@link #setMeasuredDimension(int, int)}. 8812 * <p> 8813 * The default implementation adds the RecyclerView's padding to the given bounding box 8814 * then caps the value to be within the given measurement specs. 8815 * 8816 * @param childrenBounds The bounding box of all children 8817 * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. 8818 * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. 8819 * @see #isAutoMeasureEnabled() 8820 * @see #setMeasuredDimension(int, int) 8821 */ setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec)8822 public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { 8823 int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); 8824 int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); 8825 int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); 8826 int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); 8827 setMeasuredDimension(width, height); 8828 } 8829 8830 /** 8831 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView 8832 */ requestLayout()8833 public void requestLayout() { 8834 if (mRecyclerView != null) { 8835 mRecyclerView.requestLayout(); 8836 } 8837 } 8838 8839 /** 8840 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 8841 * {@link IllegalStateException} if it <b>is not</b>. 8842 * 8843 * @param message The message for the exception. Can be null. 8844 * @see #assertNotInLayoutOrScroll(String) 8845 */ assertInLayoutOrScroll(String message)8846 public void assertInLayoutOrScroll(String message) { 8847 if (mRecyclerView != null) { 8848 mRecyclerView.assertInLayoutOrScroll(message); 8849 } 8850 } 8851 8852 /** 8853 * Chooses a size from the given specs and parameters that is closest to the desired size 8854 * and also complies with the spec. 8855 * 8856 * @param spec The measureSpec 8857 * @param desired The preferred measurement 8858 * @param min The minimum value 8859 * @return A size that fits to the given specs 8860 */ chooseSize(int spec, int desired, int min)8861 public static int chooseSize(int spec, int desired, int min) { 8862 final int mode = View.MeasureSpec.getMode(spec); 8863 final int size = View.MeasureSpec.getSize(spec); 8864 switch (mode) { 8865 case View.MeasureSpec.EXACTLY: 8866 return size; 8867 case View.MeasureSpec.AT_MOST: 8868 return Math.min(size, Math.max(desired, min)); 8869 case View.MeasureSpec.UNSPECIFIED: 8870 default: 8871 return Math.max(desired, min); 8872 } 8873 } 8874 8875 /** 8876 * Checks if RecyclerView is in the middle of a layout or scroll and throws an 8877 * {@link IllegalStateException} if it <b>is</b>. 8878 * 8879 * @param message The message for the exception. Can be null. 8880 * @see #assertInLayoutOrScroll(String) 8881 */ 8882 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly assertNotInLayoutOrScroll(String message)8883 public void assertNotInLayoutOrScroll(String message) { 8884 if (mRecyclerView != null) { 8885 mRecyclerView.assertNotInLayoutOrScroll(message); 8886 } 8887 } 8888 8889 /** 8890 * Defines whether the measuring pass of layout should use the AutoMeasure mechanism of 8891 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 8892 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 8893 * 8894 * @param enabled <code>True</code> if layout measurement should be done by the 8895 * RecyclerView, <code>false</code> if it should be done by this 8896 * LayoutManager. 8897 * @see #isAutoMeasureEnabled() 8898 * @deprecated Implementors of LayoutManager should define whether or not it uses 8899 * AutoMeasure by overriding {@link #isAutoMeasureEnabled()}. 8900 */ 8901 @Deprecated setAutoMeasureEnabled(boolean enabled)8902 public void setAutoMeasureEnabled(boolean enabled) { 8903 mAutoMeasure = enabled; 8904 } 8905 8906 /** 8907 * Returns whether the measuring pass of layout should use the AutoMeasure mechanism of 8908 * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of 8909 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 8910 * <p> 8911 * This method returns false by default (it actually returns the value passed to the 8912 * deprecated {@link #setAutoMeasureEnabled(boolean)}) and should be overridden to return 8913 * true if a LayoutManager wants to be auto measured by the RecyclerView. 8914 * <p> 8915 * If this method is overridden to return true, 8916 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} should not be overridden. 8917 * <p> 8918 * AutoMeasure is a RecyclerView mechanism that handles the measuring pass of layout in a 8919 * simple and contract satisfying way, including the wrapping of children laid out by 8920 * LayoutManager. Simply put, it handles wrapping children by calling 8921 * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a call to 8922 * {@link RecyclerView#onMeasure(int, int)}, and then calculating desired dimensions based 8923 * on children's dimensions and positions. It does this while supporting all existing 8924 * animation capabilities of the RecyclerView. 8925 * <p> 8926 * More specifically: 8927 * <ol> 8928 * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided measure 8929 * specs both have a mode of {@link View.MeasureSpec#EXACTLY}, RecyclerView will set its 8930 * measured dimensions accordingly and return, allowing layout to continue as normal 8931 * (Actually, RecyclerView will call 8932 * {@link LayoutManager#onMeasure(Recycler, State, int, int)} for backwards compatibility 8933 * reasons but it should not be overridden if AutoMeasure is being used).</li> 8934 * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the 8935 * layout process. It will first process all pending Adapter updates and 8936 * then decide whether to run a predictive layout. If it decides to do so, it will first 8937 * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to 8938 * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still 8939 * return the width and height of the RecyclerView as of the last layout calculation. 8940 * <p> 8941 * After handling the predictive case, RecyclerView will call 8942 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 8943 * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can 8944 * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, 8945 * {@link #getWidth()} and {@link #getWidthMode()}.</li> 8946 * <li>After the layout calculation, RecyclerView sets the measured width & height by 8947 * calculating the bounding box for the children (+ RecyclerView's padding). The 8948 * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose 8949 * different values. For instance, GridLayoutManager overrides this value to handle the case 8950 * where if it is vertical and has 3 columns but only 2 items, it should still measure its 8951 * width to fit 3 items, not 2.</li> 8952 * <li>Any following calls to {@link RecyclerView#onMeasure(int, int)} will run 8953 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to 8954 * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will 8955 * take care of which views are actually added / removed / moved / changed for animations so 8956 * that the LayoutManager should not worry about them and handle each 8957 * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.</li> 8958 * <li>When measure is complete and RecyclerView's 8959 * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks 8960 * whether it already did layout calculations during the measure pass and if so, it re-uses 8961 * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} 8962 * if the last measure spec was different from the final dimensions or adapter contents 8963 * have changed between the measure call and the layout call.</li> 8964 * <li>Finally, animations are calculated and run as usual.</li> 8965 * </ol> 8966 * 8967 * @return <code>True</code> if the measuring pass of layout should use the AutoMeasure 8968 * mechanism of {@link RecyclerView} or <code>False</code> if it should be done by the 8969 * LayoutManager's implementation of 8970 * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. 8971 * @see #setMeasuredDimension(Rect, int, int) 8972 * @see #onMeasure(Recycler, State, int, int) 8973 */ isAutoMeasureEnabled()8974 public boolean isAutoMeasureEnabled() { 8975 return mAutoMeasure; 8976 } 8977 8978 /** 8979 * Returns whether this LayoutManager supports "predictive item animations". 8980 * <p> 8981 * "Predictive item animations" are automatically created animations that show 8982 * where items came from, and where they are going to, as items are added, removed, 8983 * or moved within a layout. 8984 * <p> 8985 * A LayoutManager wishing to support predictive item animations must override this 8986 * method to return true (the default implementation returns false) and must obey certain 8987 * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}. 8988 * <p> 8989 * Whether item animations actually occur in a RecyclerView is actually determined by both 8990 * the return value from this method and the 8991 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the 8992 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this 8993 * method returns false, then only "simple item animations" will be enabled in the 8994 * RecyclerView, in which views whose position are changing are simply faded in/out. If the 8995 * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive 8996 * item animations will be enabled in the RecyclerView. 8997 * 8998 * @return true if this LayoutManager supports predictive item animations, false otherwise. 8999 */ supportsPredictiveItemAnimations()9000 public boolean supportsPredictiveItemAnimations() { 9001 return false; 9002 } 9003 9004 /** 9005 * Sets whether the LayoutManager should be queried for views outside of 9006 * its viewport while the UI thread is idle between frames. 9007 * 9008 * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between 9009 * view system traversals on devices running API 21 or greater. Default value is true.</p> 9010 * 9011 * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame 9012 * to RenderThread and the starting up its next frame at the next VSync pulse. By 9013 * prefetching out of window views in this time period, delays from inflation and view 9014 * binding are much less likely to cause jank and stuttering during scrolls and flings.</p> 9015 * 9016 * <p>While prefetch is enabled, it will have the side effect of expanding the effective 9017 * size of the View cache to hold prefetched views.</p> 9018 * 9019 * @param enabled <code>True</code> if items should be prefetched in between traversals. 9020 * @see #isItemPrefetchEnabled() 9021 */ setItemPrefetchEnabled(boolean enabled)9022 public final void setItemPrefetchEnabled(boolean enabled) { 9023 if (enabled != mItemPrefetchEnabled) { 9024 mItemPrefetchEnabled = enabled; 9025 mPrefetchMaxCountObserved = 0; 9026 if (mRecyclerView != null) { 9027 mRecyclerView.mRecycler.updateViewCacheSize(); 9028 } 9029 } 9030 } 9031 9032 /** 9033 * Sets whether the LayoutManager should be queried for views outside of 9034 * its viewport while the UI thread is idle between frames. 9035 * 9036 * @return true if item prefetch is enabled, false otherwise 9037 * @see #setItemPrefetchEnabled(boolean) 9038 */ isItemPrefetchEnabled()9039 public final boolean isItemPrefetchEnabled() { 9040 return mItemPrefetchEnabled; 9041 } 9042 9043 /** 9044 * Gather all positions from the LayoutManager to be prefetched, given specified momentum. 9045 * 9046 * <p>If item prefetch is enabled, this method is called in between traversals to gather 9047 * which positions the LayoutManager will soon need, given upcoming movement in subsequent 9048 * traversals.</p> 9049 * 9050 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 9051 * each item to be prepared, and these positions will have their ViewHolders created and 9052 * bound, if there is sufficient time available, in advance of being needed by a 9053 * scroll or layout.</p> 9054 * 9055 * @param dx X movement component. 9056 * @param dy Y movement component. 9057 * @param state State of RecyclerView 9058 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 9059 * @see #isItemPrefetchEnabled() 9060 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) 9061 */ 9062 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly collectAdjacentPrefetchPositions(int dx, int dy, State state, LayoutPrefetchRegistry layoutPrefetchRegistry)9063 public void collectAdjacentPrefetchPositions(int dx, int dy, State state, 9064 LayoutPrefetchRegistry layoutPrefetchRegistry) { 9065 } 9066 9067 /** 9068 * Gather all positions from the LayoutManager to be prefetched in preperation for its 9069 * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. 9070 * 9071 * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p> 9072 * 9073 * <p>If item prefetch is enabled for this LayoutManager, as well in another containing 9074 * LayoutManager, this method is called in between draw traversals to gather 9075 * which positions this LayoutManager will first need, once it appears on the screen.</p> 9076 * 9077 * <p>For example, if this LayoutManager represents a horizontally scrolling list within a 9078 * vertically scrolling LayoutManager, this method would be called when the horizontal list 9079 * is about to come onscreen.</p> 9080 * 9081 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for 9082 * each item to be prepared, and these positions will have their ViewHolders created and 9083 * bound, if there is sufficient time available, in advance of being needed by a 9084 * scroll or layout.</p> 9085 * 9086 * @param adapterItemCount number of items in the associated adapter. 9087 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. 9088 * @see #isItemPrefetchEnabled() 9089 * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) 9090 */ 9091 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly collectInitialPrefetchPositions(int adapterItemCount, LayoutPrefetchRegistry layoutPrefetchRegistry)9092 public void collectInitialPrefetchPositions(int adapterItemCount, 9093 LayoutPrefetchRegistry layoutPrefetchRegistry) { 9094 } 9095 dispatchAttachedToWindow(RecyclerView view)9096 void dispatchAttachedToWindow(RecyclerView view) { 9097 mIsAttachedToWindow = true; 9098 onAttachedToWindow(view); 9099 } 9100 dispatchDetachedFromWindow(RecyclerView view, Recycler recycler)9101 void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { 9102 mIsAttachedToWindow = false; 9103 onDetachedFromWindow(view, recycler); 9104 } 9105 9106 /** 9107 * Returns whether LayoutManager is currently attached to a RecyclerView which is attached 9108 * to a window. 9109 * 9110 * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView 9111 * is attached to window. 9112 */ isAttachedToWindow()9113 public boolean isAttachedToWindow() { 9114 return mIsAttachedToWindow; 9115 } 9116 9117 /** 9118 * Causes the Runnable to execute on the next animation time step. 9119 * The runnable will be run on the user interface thread. 9120 * <p> 9121 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 9122 * 9123 * @param action The Runnable that will be executed. 9124 * @see #removeCallbacks 9125 */ postOnAnimation(Runnable action)9126 public void postOnAnimation(Runnable action) { 9127 if (mRecyclerView != null) { 9128 ViewCompat.postOnAnimation(mRecyclerView, action); 9129 } 9130 } 9131 9132 /** 9133 * Removes the specified Runnable from the message queue. 9134 * <p> 9135 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. 9136 * 9137 * @param action The Runnable to remove from the message handling queue 9138 * @return true if RecyclerView could ask the Handler to remove the Runnable, 9139 * false otherwise. When the returned value is true, the Runnable 9140 * may or may not have been actually removed from the message queue 9141 * (for instance, if the Runnable was not in the queue already.) 9142 * @see #postOnAnimation 9143 */ removeCallbacks(Runnable action)9144 public boolean removeCallbacks(Runnable action) { 9145 if (mRecyclerView != null) { 9146 return mRecyclerView.removeCallbacks(action); 9147 } 9148 return false; 9149 } 9150 9151 /** 9152 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 9153 * is attached to a window. 9154 * <p> 9155 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 9156 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 9157 * not requested on the RecyclerView while it was detached. 9158 * <p> 9159 * Subclass implementations should always call through to the superclass implementation. 9160 * 9161 * @param view The RecyclerView this LayoutManager is bound to 9162 * @see #onDetachedFromWindow(RecyclerView, Recycler) 9163 */ 9164 @CallSuper onAttachedToWindow(RecyclerView view)9165 public void onAttachedToWindow(RecyclerView view) { 9166 } 9167 9168 /** 9169 * @deprecated override {@link #onDetachedFromWindow(RecyclerView, Recycler)} 9170 */ 9171 @Deprecated onDetachedFromWindow(RecyclerView view)9172 public void onDetachedFromWindow(RecyclerView view) { 9173 9174 } 9175 9176 /** 9177 * Called when this LayoutManager is detached from its parent RecyclerView or when 9178 * its parent RecyclerView is detached from its window. 9179 * <p> 9180 * LayoutManager should clear all of its View references as another LayoutManager might be 9181 * assigned to the RecyclerView. 9182 * <p> 9183 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not 9184 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was 9185 * not requested on the RecyclerView while it was detached. 9186 * <p> 9187 * If your LayoutManager has View references that it cleans in on-detach, it should also 9188 * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when 9189 * RecyclerView is re-attached. 9190 * <p> 9191 * Subclass implementations should always call through to the superclass implementation. 9192 * 9193 * @param view The RecyclerView this LayoutManager is bound to 9194 * @param recycler The recycler to use if you prefer to recycle your children instead of 9195 * keeping them around. 9196 * @see #onAttachedToWindow(RecyclerView) 9197 */ 9198 @CallSuper 9199 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly onDetachedFromWindow(RecyclerView view, Recycler recycler)9200 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { 9201 onDetachedFromWindow(view); 9202 } 9203 9204 /** 9205 * Check if the RecyclerView is configured to clip child views to its padding. 9206 * 9207 * @return true if this RecyclerView clips children to its padding, false otherwise 9208 */ getClipToPadding()9209 public boolean getClipToPadding() { 9210 return mRecyclerView != null && mRecyclerView.mClipToPadding; 9211 } 9212 9213 /** 9214 * Lay out all relevant child views from the given adapter. 9215 * 9216 * The LayoutManager is in charge of the behavior of item animations. By default, 9217 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple 9218 * item animations are enabled. This means that add/remove operations on the 9219 * adapter will result in animations to add new or appearing items, removed or 9220 * disappearing items, and moved items. If a LayoutManager returns false from 9221 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a 9222 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the 9223 * RecyclerView will have enough information to run those animations in a simple 9224 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will 9225 * simply fade views in and out, whether they are actually added/removed or whether 9226 * they are moved on or off the screen due to other add/remove operations. 9227 * 9228 * <p>A LayoutManager wanting a better item animation experience, where items can be 9229 * animated onto and off of the screen according to where the items exist when they 9230 * are not on screen, then the LayoutManager should return true from 9231 * {@link #supportsPredictiveItemAnimations()} and add additional logic to 9232 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations 9233 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; 9234 * once as a "pre" layout step to determine where items would have been prior to 9235 * a real layout, and again to do the "real" layout. In the pre-layout phase, 9236 * items will remember their pre-layout positions to allow them to be laid out 9237 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will 9238 * be returned from the scrap to help determine correct placement of other items. 9239 * These removed items should not be added to the child list, but should be used 9240 * to help calculate correct positioning of other views, including views that 9241 * were not previously onscreen (referred to as APPEARING views), but whose 9242 * pre-layout offscreen position can be determined given the extra 9243 * information about the pre-layout removed views.</p> 9244 * 9245 * <p>The second layout pass is the real layout in which only non-removed views 9246 * will be used. The only additional requirement during this pass is, if 9247 * {@link #supportsPredictiveItemAnimations()} returns true, to note which 9248 * views exist in the child list prior to layout and which are not there after 9249 * layout (referred to as DISAPPEARING views), and to position/layout those views 9250 * appropriately, without regard to the actual bounds of the RecyclerView. This allows 9251 * the animation system to know the location to which to animate these disappearing 9252 * views.</p> 9253 * 9254 * <p>The default LayoutManager implementations for RecyclerView handle all of these 9255 * requirements for animations already. Clients of RecyclerView can either use one 9256 * of these layout managers directly or look at their implementations of 9257 * onLayoutChildren() to see how they account for the APPEARING and 9258 * DISAPPEARING views.</p> 9259 * 9260 * @param recycler Recycler to use for fetching potentially cached views for a 9261 * position 9262 * @param state Transient state of RecyclerView 9263 */ 9264 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly onLayoutChildren(Recycler recycler, State state)9265 public void onLayoutChildren(Recycler recycler, State state) { 9266 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); 9267 } 9268 9269 /** 9270 * Called after a full layout calculation is finished. The layout calculation may include 9271 * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or 9272 * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. 9273 * This method will be called at the end of {@link View#layout(int, int, int, int)} call. 9274 * <p> 9275 * This is a good place for the LayoutManager to do some cleanup like pending scroll 9276 * position, saved state etc. 9277 * 9278 * @param state Transient state of RecyclerView 9279 */ 9280 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly onLayoutCompleted(State state)9281 public void onLayoutCompleted(State state) { 9282 } 9283 9284 /** 9285 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 9286 * 9287 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 9288 * to store extra information specific to the layout. Client code should subclass 9289 * {@link RecyclerView.LayoutParams} for this purpose.</p> 9290 * 9291 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 9292 * you must also override 9293 * {@link #checkLayoutParams(LayoutParams)}, 9294 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 9295 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 9296 * 9297 * @return A new LayoutParams for a child view 9298 */ 9299 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly generateDefaultLayoutParams()9300 public abstract LayoutParams generateDefaultLayoutParams(); 9301 9302 /** 9303 * Determines the validity of the supplied LayoutParams object. 9304 * 9305 * <p>This should check to make sure that the object is of the correct type 9306 * and all values are within acceptable ranges. The default implementation 9307 * returns <code>true</code> for non-null params.</p> 9308 * 9309 * @param lp LayoutParams object to check 9310 * @return true if this LayoutParams object is valid, false otherwise 9311 */ checkLayoutParams(LayoutParams lp)9312 public boolean checkLayoutParams(LayoutParams lp) { 9313 return lp != null; 9314 } 9315 9316 /** 9317 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 9318 * values from the supplied LayoutParams object if possible. 9319 * 9320 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 9321 * you must also override 9322 * {@link #checkLayoutParams(LayoutParams)}, 9323 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 9324 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 9325 * 9326 * @param lp Source LayoutParams object to copy values from 9327 * @return a new LayoutParams object 9328 */ 9329 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly generateLayoutParams(ViewGroup.LayoutParams lp)9330 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 9331 if (lp instanceof LayoutParams) { 9332 return new LayoutParams((LayoutParams) lp); 9333 } else if (lp instanceof MarginLayoutParams) { 9334 return new LayoutParams((MarginLayoutParams) lp); 9335 } else { 9336 return new LayoutParams(lp); 9337 } 9338 } 9339 9340 /** 9341 * Create a LayoutParams object suitable for this LayoutManager from 9342 * an inflated layout resource. 9343 * 9344 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 9345 * you must also override 9346 * {@link #checkLayoutParams(LayoutParams)}, 9347 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 9348 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 9349 * 9350 * @param c Context for obtaining styled attributes 9351 * @param attrs AttributeSet describing the supplied arguments 9352 * @return a new LayoutParams object 9353 */ 9354 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly generateLayoutParams(Context c, AttributeSet attrs)9355 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 9356 return new LayoutParams(c, attrs); 9357 } 9358 9359 /** 9360 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 9361 * The default implementation does nothing and returns 0. 9362 * 9363 * @param dx distance to scroll by in pixels. X increases as scroll position 9364 * approaches the right. 9365 * @param recycler Recycler to use for fetching potentially cached views for a 9366 * position 9367 * @param state Transient state of RecyclerView 9368 * @return The actual distance scrolled. The return value will be negative if dx was 9369 * negative and scrolling proceeeded in that direction. 9370 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 9371 */ 9372 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly scrollHorizontallyBy(int dx, Recycler recycler, State state)9373 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { 9374 return 0; 9375 } 9376 9377 /** 9378 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 9379 * The default implementation does nothing and returns 0. 9380 * 9381 * @param dy distance to scroll in pixels. Y increases as scroll position 9382 * approaches the bottom. 9383 * @param recycler Recycler to use for fetching potentially cached views for a 9384 * position 9385 * @param state Transient state of RecyclerView 9386 * @return The actual distance scrolled. The return value will be negative if dy was 9387 * negative and scrolling proceeeded in that direction. 9388 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 9389 */ 9390 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly scrollVerticallyBy(int dy, Recycler recycler, State state)9391 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { 9392 return 0; 9393 } 9394 9395 /** 9396 * Query if horizontal scrolling is currently supported. The default implementation 9397 * returns false. 9398 * 9399 * @return True if this LayoutManager can scroll the current contents horizontally 9400 */ canScrollHorizontally()9401 public boolean canScrollHorizontally() { 9402 return false; 9403 } 9404 9405 /** 9406 * Query if vertical scrolling is currently supported. The default implementation 9407 * returns false. 9408 * 9409 * @return True if this LayoutManager can scroll the current contents vertically 9410 */ canScrollVertically()9411 public boolean canScrollVertically() { 9412 return false; 9413 } 9414 9415 /** 9416 * Scroll to the specified adapter position. 9417 * 9418 * Actual position of the item on the screen depends on the LayoutManager implementation. 9419 * 9420 * @param position Scroll to this adapter position. 9421 */ scrollToPosition(int position)9422 public void scrollToPosition(int position) { 9423 if (sVerboseLoggingEnabled) { 9424 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); 9425 } 9426 } 9427 9428 /** 9429 * <p>Smooth scroll to the specified adapter position.</p> 9430 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} 9431 * instance and call {@link #startSmoothScroll(SmoothScroller)}. 9432 * </p> 9433 * 9434 * @param recyclerView The RecyclerView to which this layout manager is attached 9435 * @param state Current State of RecyclerView 9436 * @param position Scroll to this adapter position. 9437 */ 9438 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly smoothScrollToPosition(RecyclerView recyclerView, State state, int position)9439 public void smoothScrollToPosition(RecyclerView recyclerView, State state, 9440 int position) { 9441 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); 9442 } 9443 9444 /** 9445 * Starts a smooth scroll using the provided {@link SmoothScroller}. 9446 * 9447 * <p>Each instance of SmoothScroller is intended to only be used once. Provide a new 9448 * SmoothScroller instance each time this method is called. 9449 * 9450 * <p>Calling this method will cancel any previous smooth scroll request. 9451 * 9452 * @param smoothScroller Instance which defines how smooth scroll should be animated 9453 */ 9454 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly startSmoothScroll(SmoothScroller smoothScroller)9455 public void startSmoothScroll(SmoothScroller smoothScroller) { 9456 if (mSmoothScroller != null && smoothScroller != mSmoothScroller 9457 && mSmoothScroller.isRunning()) { 9458 mSmoothScroller.stop(); 9459 } 9460 mSmoothScroller = smoothScroller; 9461 mSmoothScroller.start(mRecyclerView, this); 9462 } 9463 9464 /** 9465 * @return true if RecyclerView is currently in the state of smooth scrolling. 9466 */ isSmoothScrolling()9467 public boolean isSmoothScrolling() { 9468 return mSmoothScroller != null && mSmoothScroller.isRunning(); 9469 } 9470 9471 /** 9472 * Returns the resolved layout direction for this RecyclerView. 9473 * 9474 * @return {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout 9475 * direction is RTL or returns 9476 * {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction 9477 * is not RTL. 9478 */ getLayoutDirection()9479 public int getLayoutDirection() { 9480 return mRecyclerView.getLayoutDirection(); 9481 } 9482 9483 /** 9484 * Query if the layout is in reverse order. This will affect, for example, keyboard 9485 * navigation via page up/page down. The default implementation returns false. 9486 * 9487 * @return true if this LayoutManager is currently in reverse order. 9488 */ isLayoutReversed()9489 public boolean isLayoutReversed() { 9490 return false; 9491 } 9492 9493 /** 9494 * Ends all animations on the view created by the {@link ItemAnimator}. 9495 * 9496 * @param view The View for which the animations should be ended. 9497 * @see RecyclerView.ItemAnimator#endAnimations() 9498 */ 9499 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly endAnimation(View view)9500 public void endAnimation(View view) { 9501 if (mRecyclerView.mItemAnimator != null) { 9502 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); 9503 } 9504 } 9505 9506 /** 9507 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 9508 * to the layout that is known to be going away, either because it has been 9509 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 9510 * visible portion of the container but is being laid out in order to inform RecyclerView 9511 * in how to animate the item out of view. 9512 * <p> 9513 * Views added via this method are going to be invisible to LayoutManager after the 9514 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 9515 * or won't be included in {@link #getChildCount()} method. 9516 * 9517 * @param child View to add and then remove with animation. 9518 */ 9519 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly addDisappearingView(View child)9520 public void addDisappearingView(View child) { 9521 addDisappearingView(child, -1); 9522 } 9523 9524 /** 9525 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view 9526 * to the layout that is known to be going away, either because it has been 9527 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the 9528 * visible portion of the container but is being laid out in order to inform RecyclerView 9529 * in how to animate the item out of view. 9530 * <p> 9531 * Views added via this method are going to be invisible to LayoutManager after the 9532 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} 9533 * or won't be included in {@link #getChildCount()} method. 9534 * 9535 * @param child View to add and then remove with animation. 9536 * @param index Index of the view. 9537 */ 9538 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly addDisappearingView(View child, int index)9539 public void addDisappearingView(View child, int index) { 9540 addViewInt(child, index, true); 9541 } 9542 9543 /** 9544 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 9545 * use this method to add views obtained from a {@link Recycler} using 9546 * {@link Recycler#getViewForPosition(int)}. 9547 * 9548 * @param child View to add 9549 */ 9550 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly addView(View child)9551 public void addView(View child) { 9552 addView(child, -1); 9553 } 9554 9555 /** 9556 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 9557 * use this method to add views obtained from a {@link Recycler} using 9558 * {@link Recycler#getViewForPosition(int)}. 9559 * 9560 * @param child View to add 9561 * @param index Index to add child at 9562 */ 9563 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly addView(View child, int index)9564 public void addView(View child, int index) { 9565 addViewInt(child, index, false); 9566 } 9567 addViewInt(View child, int index, boolean disappearing)9568 private void addViewInt(View child, int index, boolean disappearing) { 9569 final ViewHolder holder = getChildViewHolderInt(child); 9570 if (disappearing || holder.isRemoved()) { 9571 // these views will be hidden at the end of the layout pass. 9572 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); 9573 } else { 9574 // This may look like unnecessary but may happen if layout manager supports 9575 // predictive layouts and adapter removed then re-added the same item. 9576 // In this case, added version will be visible in the post layout (because add is 9577 // deferred) but RV will still bind it to the same View. 9578 // So if a View re-appears in post layout pass, remove it from disappearing list. 9579 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); 9580 } 9581 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 9582 if (holder.wasReturnedFromScrap() || holder.isScrap()) { 9583 if (holder.isScrap()) { 9584 holder.unScrap(); 9585 } else { 9586 holder.clearReturnedFromScrapFlag(); 9587 } 9588 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); 9589 if (DISPATCH_TEMP_DETACH) { 9590 ViewCompat.dispatchFinishTemporaryDetach(child); 9591 } 9592 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child 9593 // ensure in correct position 9594 int currentIndex = mChildHelper.indexOfChild(child); 9595 if (index == -1) { 9596 index = mChildHelper.getChildCount(); 9597 } 9598 if (currentIndex == -1) { 9599 throw new IllegalStateException("Added View has RecyclerView as parent but" 9600 + " view is not a real child. Unfiltered index:" 9601 + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel()); 9602 } 9603 if (currentIndex != index) { 9604 mRecyclerView.mLayout.moveView(currentIndex, index); 9605 } 9606 } else { 9607 mChildHelper.addView(child, index, false); 9608 lp.mInsetsDirty = true; 9609 if (mSmoothScroller != null && mSmoothScroller.isRunning()) { 9610 mSmoothScroller.onChildAttachedToWindow(child); 9611 } 9612 } 9613 if (lp.mPendingInvalidate) { 9614 if (sVerboseLoggingEnabled) { 9615 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); 9616 } 9617 holder.itemView.invalidate(); 9618 lp.mPendingInvalidate = false; 9619 } 9620 } 9621 9622 /** 9623 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 9624 * use this method to completely remove a child view that is no longer needed. 9625 * LayoutManagers should strongly consider recycling removed views using 9626 * {@link Recycler#recycleView(android.view.View)}. 9627 * 9628 * @param child View to remove 9629 */ 9630 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly removeView(View child)9631 public void removeView(View child) { 9632 mChildHelper.removeView(child); 9633 } 9634 9635 /** 9636 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 9637 * use this method to completely remove a child view that is no longer needed. 9638 * LayoutManagers should strongly consider recycling removed views using 9639 * {@link Recycler#recycleView(android.view.View)}. 9640 * 9641 * @param index Index of the child view to remove 9642 */ removeViewAt(int index)9643 public void removeViewAt(int index) { 9644 final View child = getChildAt(index); 9645 if (child != null) { 9646 mChildHelper.removeViewAt(index); 9647 } 9648 } 9649 9650 /** 9651 * Remove all views from the currently attached RecyclerView. This will not recycle 9652 * any of the affected views; the LayoutManager is responsible for doing so if desired. 9653 */ removeAllViews()9654 public void removeAllViews() { 9655 // Only remove non-animating views 9656 final int childCount = getChildCount(); 9657 for (int i = childCount - 1; i >= 0; i--) { 9658 mChildHelper.removeViewAt(i); 9659 } 9660 } 9661 9662 /** 9663 * Returns offset of the RecyclerView's text baseline from the its top boundary. 9664 * 9665 * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if 9666 * there is no baseline. 9667 */ getBaseline()9668 public int getBaseline() { 9669 return -1; 9670 } 9671 9672 /** 9673 * Returns the adapter position of the item represented by the given View. This does not 9674 * contain any adapter changes that might have happened after the last layout. 9675 * 9676 * @param view The view to query 9677 * @return The adapter position of the item which is rendered by this View. 9678 */ getPosition(@onNull View view)9679 public int getPosition(@NonNull View view) { 9680 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 9681 } 9682 9683 /** 9684 * Returns the View type defined by the adapter. 9685 * 9686 * @param view The view to query 9687 * @return The type of the view assigned by the adapter. 9688 */ getItemViewType(@onNull View view)9689 public int getItemViewType(@NonNull View view) { 9690 return getChildViewHolderInt(view).getItemViewType(); 9691 } 9692 9693 /** 9694 * Traverses the ancestors of the given view and returns the item view that contains it 9695 * and also a direct child of the LayoutManager. 9696 * <p> 9697 * Note that this method may return null if the view is a child of the RecyclerView but 9698 * not a child of the LayoutManager (e.g. running a disappear animation). 9699 * 9700 * @param view The view that is a descendant of the LayoutManager. 9701 * @return The direct child of the LayoutManager which contains the given view or null if 9702 * the provided view is not a descendant of this LayoutManager. 9703 * @see RecyclerView#getChildViewHolder(View) 9704 * @see RecyclerView#findContainingViewHolder(View) 9705 */ findContainingItemView(@onNull View view)9706 public @Nullable View findContainingItemView(@NonNull View view) { 9707 if (mRecyclerView == null) { 9708 return null; 9709 } 9710 View found = mRecyclerView.findContainingItemView(view); 9711 if (found == null) { 9712 return null; 9713 } 9714 if (mChildHelper.isHidden(found)) { 9715 return null; 9716 } 9717 return found; 9718 } 9719 9720 /** 9721 * Finds the view which represents the given adapter position. 9722 * <p> 9723 * This method traverses each child since it has no information about child order. 9724 * Override this method to improve performance if your LayoutManager keeps data about 9725 * child views. 9726 * <p> 9727 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. 9728 * 9729 * @param position Position of the item in adapter 9730 * @return The child view that represents the given position or null if the position is not 9731 * laid out 9732 */ findViewByPosition(int position)9733 public @Nullable View findViewByPosition(int position) { 9734 final int childCount = getChildCount(); 9735 for (int i = 0; i < childCount; i++) { 9736 View child = getChildAt(i); 9737 ViewHolder vh = getChildViewHolderInt(child); 9738 if (vh == null) { 9739 continue; 9740 } 9741 if (vh.getLayoutPosition() == position && !vh.shouldIgnore() 9742 && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { 9743 return child; 9744 } 9745 } 9746 return null; 9747 } 9748 9749 /** 9750 * Temporarily detach a child view. 9751 * 9752 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 9753 * views currently attached to the RecyclerView. Generally LayoutManager implementations 9754 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 9755 * so that the detached view may be rebound and reused.</p> 9756 * 9757 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 9758 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 9759 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 9760 * before the LayoutManager entry point method called by RecyclerView returns.</p> 9761 * 9762 * @param child Child to detach 9763 */ detachView(@onNull View child)9764 public void detachView(@NonNull View child) { 9765 final int ind = mChildHelper.indexOfChild(child); 9766 if (ind >= 0) { 9767 detachViewInternal(ind, child); 9768 } 9769 } 9770 9771 /** 9772 * Temporarily detach a child view. 9773 * 9774 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 9775 * views currently attached to the RecyclerView. Generally LayoutManager implementations 9776 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 9777 * so that the detached view may be rebound and reused.</p> 9778 * 9779 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 9780 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 9781 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 9782 * before the LayoutManager entry point method called by RecyclerView returns.</p> 9783 * 9784 * @param index Index of the child to detach 9785 */ detachViewAt(int index)9786 public void detachViewAt(int index) { 9787 detachViewInternal(index, getChildAt(index)); 9788 } 9789 detachViewInternal(int index, @NonNull View view)9790 private void detachViewInternal(int index, @NonNull View view) { 9791 if (DISPATCH_TEMP_DETACH) { 9792 ViewCompat.dispatchStartTemporaryDetach(view); 9793 } 9794 mChildHelper.detachViewFromParent(index); 9795 } 9796 9797 /** 9798 * Reattach a previously {@link #detachView(android.view.View) detached} view. 9799 * This method should not be used to reattach views that were previously 9800 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 9801 * 9802 * @param child Child to reattach 9803 * @param index Intended child index for child 9804 * @param lp LayoutParams for child 9805 */ attachView(@onNull View child, int index, LayoutParams lp)9806 public void attachView(@NonNull View child, int index, LayoutParams lp) { 9807 ViewHolder vh = getChildViewHolderInt(child); 9808 if (vh.isRemoved()) { 9809 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); 9810 } else { 9811 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); 9812 } 9813 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); 9814 if (DISPATCH_TEMP_DETACH) { 9815 ViewCompat.dispatchFinishTemporaryDetach(child); 9816 } 9817 } 9818 9819 /** 9820 * Reattach a previously {@link #detachView(android.view.View) detached} view. 9821 * This method should not be used to reattach views that were previously 9822 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 9823 * 9824 * @param child Child to reattach 9825 * @param index Intended child index for child 9826 */ attachView(@onNull View child, int index)9827 public void attachView(@NonNull View child, int index) { 9828 attachView(child, index, (LayoutParams) child.getLayoutParams()); 9829 } 9830 9831 /** 9832 * Reattach a previously {@link #detachView(android.view.View) detached} view. 9833 * This method should not be used to reattach views that were previously 9834 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 9835 * 9836 * @param child Child to reattach 9837 */ attachView(@onNull View child)9838 public void attachView(@NonNull View child) { 9839 attachView(child, -1); 9840 } 9841 9842 /** 9843 * Finish removing a view that was previously temporarily 9844 * {@link #detachView(android.view.View) detached}. 9845 * 9846 * @param child Detached child to remove 9847 */ removeDetachedView(@onNull View child)9848 public void removeDetachedView(@NonNull View child) { 9849 mRecyclerView.removeDetachedView(child, false); 9850 } 9851 9852 /** 9853 * Moves a View from one position to another. 9854 * 9855 * @param fromIndex The View's initial index 9856 * @param toIndex The View's target index 9857 */ moveView(int fromIndex, int toIndex)9858 public void moveView(int fromIndex, int toIndex) { 9859 View view = getChildAt(fromIndex); 9860 if (view == null) { 9861 throw new IllegalArgumentException("Cannot move a child from non-existing index:" 9862 + fromIndex + mRecyclerView.toString()); 9863 } 9864 detachViewAt(fromIndex); 9865 attachView(view, toIndex); 9866 } 9867 9868 /** 9869 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 9870 * 9871 * <p>Scrapping a view allows it to be rebound and reused to show updated or 9872 * different data.</p> 9873 * 9874 * @param child Child to detach and scrap 9875 * @param recycler Recycler to deposit the new scrap view into 9876 */ detachAndScrapView(@onNull View child, @NonNull Recycler recycler)9877 public void detachAndScrapView(@NonNull View child, @NonNull Recycler recycler) { 9878 int index = mChildHelper.indexOfChild(child); 9879 scrapOrRecycleView(recycler, index, child); 9880 } 9881 9882 /** 9883 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 9884 * 9885 * <p>Scrapping a view allows it to be rebound and reused to show updated or 9886 * different data.</p> 9887 * 9888 * @param index Index of child to detach and scrap 9889 * @param recycler Recycler to deposit the new scrap view into 9890 */ detachAndScrapViewAt(int index, @NonNull Recycler recycler)9891 public void detachAndScrapViewAt(int index, @NonNull Recycler recycler) { 9892 final View child = getChildAt(index); 9893 scrapOrRecycleView(recycler, index, child); 9894 } 9895 9896 /** 9897 * Remove a child view and recycle it using the given Recycler. 9898 * 9899 * @param child Child to remove and recycle 9900 * @param recycler Recycler to use to recycle child 9901 */ removeAndRecycleView(@onNull View child, @NonNull Recycler recycler)9902 public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) { 9903 removeView(child); 9904 recycler.recycleView(child); 9905 } 9906 9907 /** 9908 * Remove a child view and recycle it using the given Recycler. 9909 * 9910 * @param index Index of child to remove and recycle 9911 * @param recycler Recycler to use to recycle child 9912 */ removeAndRecycleViewAt(int index, @NonNull Recycler recycler)9913 public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { 9914 final View view = getChildAt(index); 9915 removeViewAt(index); 9916 recycler.recycleView(view); 9917 } 9918 9919 /** 9920 * Return the current number of child views attached to the parent RecyclerView. 9921 * This does not include child views that were temporarily detached and/or scrapped. 9922 * 9923 * @return Number of attached children 9924 */ getChildCount()9925 public int getChildCount() { 9926 return mChildHelper != null ? mChildHelper.getChildCount() : 0; 9927 } 9928 9929 /** 9930 * Return the child view at the given index 9931 * 9932 * @param index Index of child to return 9933 * @return Child view at index 9934 */ getChildAt(int index)9935 public @Nullable View getChildAt(int index) { 9936 return mChildHelper != null ? mChildHelper.getChildAt(index) : null; 9937 } 9938 9939 /** 9940 * Return the width measurement spec mode that is currently relevant to the LayoutManager. 9941 * 9942 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 9943 * {@link #setAutoMeasureEnabled(boolean)}. 9944 * 9945 * <p>When RecyclerView is running a layout, this value is always set to 9946 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 9947 * 9948 * @return Width measure spec mode 9949 * @see View.MeasureSpec#getMode(int) 9950 */ getWidthMode()9951 public int getWidthMode() { 9952 return mWidthMode; 9953 } 9954 9955 /** 9956 * Return the height measurement spec mode that is currently relevant to the LayoutManager. 9957 * 9958 * <p>This value is set only if the LayoutManager opts into the AutoMeasure api via 9959 * {@link #setAutoMeasureEnabled(boolean)}. 9960 * 9961 * <p>When RecyclerView is running a layout, this value is always set to 9962 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. 9963 * 9964 * @return Height measure spec mode 9965 * @see View.MeasureSpec#getMode(int) 9966 */ getHeightMode()9967 public int getHeightMode() { 9968 return mHeightMode; 9969 } 9970 9971 /** 9972 * Returns the width that is currently relevant to the LayoutManager. 9973 * 9974 * <p>This value is usually equal to the laid out width of the {@link RecyclerView} but may 9975 * reflect the current {@link android.view.View.MeasureSpec} width if the 9976 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 9977 * measuring. The LayoutManager must always use this method to retrieve the width relevant 9978 * to it at any given time. 9979 * 9980 * @return Width in pixels 9981 */ 9982 @Px getWidth()9983 public int getWidth() { 9984 return mWidth; 9985 } 9986 9987 /** 9988 * Returns the height that is currently relevant to the LayoutManager. 9989 * 9990 * <p>This value is usually equal to the laid out height of the {@link RecyclerView} but may 9991 * reflect the current {@link android.view.View.MeasureSpec} height if the 9992 * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of 9993 * measuring. The LayoutManager must always use this method to retrieve the height relevant 9994 * to it at any given time. 9995 * 9996 * @return Height in pixels 9997 */ 9998 @Px getHeight()9999 public int getHeight() { 10000 return mHeight; 10001 } 10002 10003 /** 10004 * Return the left padding of the parent RecyclerView 10005 * 10006 * @return Padding in pixels 10007 */ 10008 @Px getPaddingLeft()10009 public int getPaddingLeft() { 10010 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 10011 } 10012 10013 /** 10014 * Return the top padding of the parent RecyclerView 10015 * 10016 * @return Padding in pixels 10017 */ 10018 @Px getPaddingTop()10019 public int getPaddingTop() { 10020 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 10021 } 10022 10023 /** 10024 * Return the right padding of the parent RecyclerView 10025 * 10026 * @return Padding in pixels 10027 */ 10028 @Px getPaddingRight()10029 public int getPaddingRight() { 10030 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 10031 } 10032 10033 /** 10034 * Return the bottom padding of the parent RecyclerView 10035 * 10036 * @return Padding in pixels 10037 */ 10038 @Px getPaddingBottom()10039 public int getPaddingBottom() { 10040 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 10041 } 10042 10043 /** 10044 * Return the start padding of the parent RecyclerView 10045 * 10046 * @return Padding in pixels 10047 */ 10048 @Px getPaddingStart()10049 public int getPaddingStart() { 10050 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 10051 } 10052 10053 /** 10054 * Return the end padding of the parent RecyclerView 10055 * 10056 * @return Padding in pixels 10057 */ 10058 @Px getPaddingEnd()10059 public int getPaddingEnd() { 10060 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 10061 } 10062 10063 /** 10064 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 10065 * 10066 * @return True if the RecyclerView has focus, false otherwise. 10067 * @see View#isFocused() 10068 */ isFocused()10069 public boolean isFocused() { 10070 return mRecyclerView != null && mRecyclerView.isFocused(); 10071 } 10072 10073 /** 10074 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 10075 * 10076 * @return true if the RecyclerView has or contains focus 10077 * @see View#hasFocus() 10078 */ hasFocus()10079 public boolean hasFocus() { 10080 return mRecyclerView != null && mRecyclerView.hasFocus(); 10081 } 10082 10083 /** 10084 * Returns the item View which has or contains focus. 10085 * 10086 * @return A direct child of RecyclerView which has focus or contains the focused child. 10087 */ getFocusedChild()10088 public @Nullable View getFocusedChild() { 10089 if (mRecyclerView == null) { 10090 return null; 10091 } 10092 final View focused = mRecyclerView.getFocusedChild(); 10093 if (focused == null || mChildHelper.isHidden(focused)) { 10094 return null; 10095 } 10096 return focused; 10097 } 10098 10099 /** 10100 * Returns the number of items in the adapter bound to the parent RecyclerView. 10101 * <p> 10102 * Note that this number is not necessarily equal to 10103 * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is 10104 * available, you should use {@link State#getItemCount() State#getItemCount()} instead. 10105 * For more details, check the documentation for 10106 * {@link State#getItemCount() State#getItemCount()}. 10107 * 10108 * @return The number of items in the bound adapter 10109 * @see State#getItemCount() 10110 */ getItemCount()10111 public int getItemCount() { 10112 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 10113 return a != null ? a.getItemCount() : 0; 10114 } 10115 10116 /** 10117 * Offset all child views attached to the parent RecyclerView by dx pixels along 10118 * the horizontal axis. 10119 * 10120 * @param dx Pixels to offset by 10121 */ offsetChildrenHorizontal(@x int dx)10122 public void offsetChildrenHorizontal(@Px int dx) { 10123 if (mRecyclerView != null) { 10124 mRecyclerView.offsetChildrenHorizontal(dx); 10125 } 10126 } 10127 10128 /** 10129 * Offset all child views attached to the parent RecyclerView by dy pixels along 10130 * the vertical axis. 10131 * 10132 * @param dy Pixels to offset by 10133 */ offsetChildrenVertical(@x int dy)10134 public void offsetChildrenVertical(@Px int dy) { 10135 if (mRecyclerView != null) { 10136 mRecyclerView.offsetChildrenVertical(dy); 10137 } 10138 } 10139 10140 /** 10141 * Flags a view so that it will not be scrapped or recycled. 10142 * <p> 10143 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and 10144 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child 10145 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not 10146 * ignore the child. 10147 * <p> 10148 * Before this child can be recycled again, you have to call 10149 * {@link #stopIgnoringView(View)}. 10150 * <p> 10151 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 10152 * 10153 * @param view View to ignore. 10154 * @see #stopIgnoringView(View) 10155 */ ignoreView(@onNull View view)10156 public void ignoreView(@NonNull View view) { 10157 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { 10158 // checking this because calling this method on a recycled or detached view may 10159 // cause loss of state. 10160 throw new IllegalArgumentException("View should be fully attached to be ignored" 10161 + mRecyclerView.exceptionLabel()); 10162 } 10163 final ViewHolder vh = getChildViewHolderInt(view); 10164 vh.addFlags(ViewHolder.FLAG_IGNORE); 10165 mRecyclerView.mViewInfoStore.removeViewHolder(vh); 10166 } 10167 10168 /** 10169 * View can be scrapped and recycled again. 10170 * <p> 10171 * Note that calling this method removes all information in the view holder. 10172 * <p> 10173 * You can call this method only if your LayoutManger is in onLayout or onScroll callback. 10174 * 10175 * @param view View to ignore. 10176 */ stopIgnoringView(@onNull View view)10177 public void stopIgnoringView(@NonNull View view) { 10178 final ViewHolder vh = getChildViewHolderInt(view); 10179 vh.stopIgnoring(); 10180 vh.resetInternal(); 10181 vh.addFlags(ViewHolder.FLAG_INVALID); 10182 } 10183 10184 /** 10185 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 10186 * into the given Recycler. The Recycler may prefer to reuse scrap views before 10187 * other views that were previously recycled. 10188 * 10189 * @param recycler Recycler to scrap views into 10190 */ detachAndScrapAttachedViews(@onNull Recycler recycler)10191 public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { 10192 final int childCount = getChildCount(); 10193 for (int i = childCount - 1; i >= 0; i--) { 10194 final View v = getChildAt(i); 10195 scrapOrRecycleView(recycler, i, v); 10196 } 10197 } 10198 scrapOrRecycleView(Recycler recycler, int index, View view)10199 private void scrapOrRecycleView(Recycler recycler, int index, View view) { 10200 final ViewHolder viewHolder = getChildViewHolderInt(view); 10201 if (viewHolder.shouldIgnore()) { 10202 if (sVerboseLoggingEnabled) { 10203 Log.d(TAG, "ignoring view " + viewHolder); 10204 } 10205 return; 10206 } 10207 if (viewHolder.isInvalid() && !viewHolder.isRemoved() 10208 && !mRecyclerView.mAdapter.hasStableIds()) { 10209 removeViewAt(index); 10210 recycler.recycleViewHolderInternal(viewHolder); 10211 } else { 10212 detachViewAt(index); 10213 recycler.scrapView(view); 10214 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); 10215 } 10216 } 10217 10218 /** 10219 * Recycles the scrapped views. 10220 * <p> 10221 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is 10222 * the expected behavior if scrapped views are used for animations. Otherwise, we need to 10223 * call remove and invalidate RecyclerView to ensure UI update. 10224 * 10225 * @param recycler Recycler 10226 */ removeAndRecycleScrapInt(Recycler recycler)10227 void removeAndRecycleScrapInt(Recycler recycler) { 10228 final int scrapCount = recycler.getScrapCount(); 10229 // Loop backward, recycler might be changed by removeDetachedView() 10230 for (int i = scrapCount - 1; i >= 0; i--) { 10231 final View scrap = recycler.getScrapViewAt(i); 10232 final ViewHolder vh = getChildViewHolderInt(scrap); 10233 if (vh.shouldIgnore()) { 10234 continue; 10235 } 10236 // If the scrap view is animating, we need to cancel them first. If we cancel it 10237 // here, ItemAnimator callback may recycle it which will cause double recycling. 10238 // To avoid this, we mark it as not recyclable before calling the item animator. 10239 // Since removeDetachedView calls a user API, a common mistake (ending animations on 10240 // the view) may recycle it too, so we guard it before we call user APIs. 10241 vh.setIsRecyclable(false); 10242 if (vh.isTmpDetached()) { 10243 mRecyclerView.removeDetachedView(scrap, false); 10244 } 10245 if (mRecyclerView.mItemAnimator != null) { 10246 mRecyclerView.mItemAnimator.endAnimation(vh); 10247 } 10248 vh.setIsRecyclable(true); 10249 recycler.quickRecycleScrapView(scrap); 10250 } 10251 recycler.clearScrap(); 10252 if (scrapCount > 0) { 10253 mRecyclerView.invalidate(); 10254 } 10255 } 10256 10257 10258 /** 10259 * Measure a child view using standard measurement policy, taking the padding 10260 * of the parent RecyclerView and any added item decorations into account. 10261 * 10262 * <p>If the RecyclerView can be scrolled in either dimension the caller may 10263 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 10264 * 10265 * @param child Child view to measure 10266 * @param widthUsed Width in pixels currently consumed by other views, if relevant 10267 * @param heightUsed Height in pixels currently consumed by other views, if relevant 10268 */ measureChild(@onNull View child, int widthUsed, int heightUsed)10269 public void measureChild(@NonNull View child, int widthUsed, int heightUsed) { 10270 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 10271 10272 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 10273 widthUsed += insets.left + insets.right; 10274 heightUsed += insets.top + insets.bottom; 10275 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 10276 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 10277 canScrollHorizontally()); 10278 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 10279 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 10280 canScrollVertically()); 10281 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 10282 child.measure(widthSpec, heightSpec); 10283 } 10284 } 10285 10286 /** 10287 * RecyclerView internally does its own View measurement caching which should help with 10288 * WRAP_CONTENT. 10289 * <p> 10290 * Use this method if the View is already measured once in this layout pass. 10291 */ shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp)10292 boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 10293 return !mMeasurementCacheEnabled 10294 || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) 10295 || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); 10296 } 10297 10298 // we may consider making this public 10299 10300 /** 10301 * RecyclerView internally does its own View measurement caching which should help with 10302 * WRAP_CONTENT. 10303 * <p> 10304 * Use this method if the View is not yet measured and you need to decide whether to 10305 * measure this View or not. 10306 */ shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp)10307 boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { 10308 return child.isLayoutRequested() 10309 || !mMeasurementCacheEnabled 10310 || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) 10311 || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); 10312 } 10313 10314 /** 10315 * In addition to the View Framework's measurement cache, RecyclerView uses its own 10316 * additional measurement cache for its children to avoid re-measuring them when not 10317 * necessary. It is on by default but it can be turned off via 10318 * {@link #setMeasurementCacheEnabled(boolean)}. 10319 * 10320 * @return True if measurement cache is enabled, false otherwise. 10321 * @see #setMeasurementCacheEnabled(boolean) 10322 */ isMeasurementCacheEnabled()10323 public boolean isMeasurementCacheEnabled() { 10324 return mMeasurementCacheEnabled; 10325 } 10326 10327 /** 10328 * Sets whether RecyclerView should use its own measurement cache for the children. This is 10329 * a more aggressive cache than the framework uses. 10330 * 10331 * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. 10332 * @see #isMeasurementCacheEnabled() 10333 */ setMeasurementCacheEnabled(boolean measurementCacheEnabled)10334 public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { 10335 mMeasurementCacheEnabled = measurementCacheEnabled; 10336 } 10337 isMeasurementUpToDate(int childSize, int spec, int dimension)10338 private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { 10339 final int specMode = MeasureSpec.getMode(spec); 10340 final int specSize = MeasureSpec.getSize(spec); 10341 if (dimension > 0 && childSize != dimension) { 10342 return false; 10343 } 10344 switch (specMode) { 10345 case MeasureSpec.UNSPECIFIED: 10346 return true; 10347 case MeasureSpec.AT_MOST: 10348 return specSize >= childSize; 10349 case MeasureSpec.EXACTLY: 10350 return specSize == childSize; 10351 } 10352 return false; 10353 } 10354 10355 /** 10356 * Measure a child view using standard measurement policy, taking the padding 10357 * of the parent RecyclerView, any added item decorations and the child margins 10358 * into account. 10359 * 10360 * <p>If the RecyclerView can be scrolled in either dimension the caller may 10361 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 10362 * 10363 * @param child Child view to measure 10364 * @param widthUsed Width in pixels currently consumed by other views, if relevant 10365 * @param heightUsed Height in pixels currently consumed by other views, if relevant 10366 */ measureChildWithMargins(@onNull View child, int widthUsed, int heightUsed)10367 public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { 10368 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 10369 10370 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 10371 widthUsed += insets.left + insets.right; 10372 heightUsed += insets.top + insets.bottom; 10373 10374 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 10375 getPaddingLeft() + getPaddingRight() 10376 + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 10377 canScrollHorizontally()); 10378 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 10379 getPaddingTop() + getPaddingBottom() 10380 + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 10381 canScrollVertically()); 10382 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 10383 child.measure(widthSpec, heightSpec); 10384 } 10385 } 10386 10387 /** 10388 * Calculate a MeasureSpec value for measuring a child view in one dimension. 10389 * 10390 * @param parentSize Size of the parent view where the child will be placed 10391 * @param padding Total space currently consumed by other elements of the parent 10392 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 10393 * Generally obtained from the child view's LayoutParams 10394 * @param canScroll true if the parent RecyclerView can scroll in this dimension 10395 * @return a MeasureSpec value for the child view 10396 * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} 10397 */ 10398 @Deprecated getChildMeasureSpec(int parentSize, int padding, int childDimension, boolean canScroll)10399 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 10400 boolean canScroll) { 10401 int size = Math.max(0, parentSize - padding); 10402 int resultSize = 0; 10403 int resultMode = 0; 10404 if (canScroll) { 10405 if (childDimension >= 0) { 10406 resultSize = childDimension; 10407 resultMode = MeasureSpec.EXACTLY; 10408 } else { 10409 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 10410 // instead using UNSPECIFIED. 10411 resultSize = 0; 10412 resultMode = MeasureSpec.UNSPECIFIED; 10413 } 10414 } else { 10415 if (childDimension >= 0) { 10416 resultSize = childDimension; 10417 resultMode = MeasureSpec.EXACTLY; 10418 } else if (childDimension == LayoutParams.MATCH_PARENT) { 10419 resultSize = size; 10420 // TODO this should be my spec. 10421 resultMode = MeasureSpec.EXACTLY; 10422 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 10423 resultSize = size; 10424 resultMode = MeasureSpec.AT_MOST; 10425 } 10426 } 10427 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 10428 } 10429 10430 /** 10431 * Calculate a MeasureSpec value for measuring a child view in one dimension. 10432 * 10433 * @param parentSize Size of the parent view where the child will be placed 10434 * @param parentMode The measurement spec mode of the parent 10435 * @param padding Total space currently consumed by other elements of parent 10436 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 10437 * Generally obtained from the child view's LayoutParams 10438 * @param canScroll true if the parent RecyclerView can scroll in this dimension 10439 * @return a MeasureSpec value for the child view 10440 */ getChildMeasureSpec(int parentSize, int parentMode, int padding, int childDimension, boolean canScroll)10441 public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, 10442 int childDimension, boolean canScroll) { 10443 int size = Math.max(0, parentSize - padding); 10444 int resultSize = 0; 10445 int resultMode = 0; 10446 if (canScroll) { 10447 if (childDimension >= 0) { 10448 resultSize = childDimension; 10449 resultMode = MeasureSpec.EXACTLY; 10450 } else if (childDimension == LayoutParams.MATCH_PARENT) { 10451 switch (parentMode) { 10452 case MeasureSpec.AT_MOST: 10453 case MeasureSpec.EXACTLY: 10454 resultSize = size; 10455 resultMode = parentMode; 10456 break; 10457 case MeasureSpec.UNSPECIFIED: 10458 resultSize = 0; 10459 resultMode = MeasureSpec.UNSPECIFIED; 10460 break; 10461 } 10462 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 10463 resultSize = 0; 10464 resultMode = MeasureSpec.UNSPECIFIED; 10465 } 10466 } else { 10467 if (childDimension >= 0) { 10468 resultSize = childDimension; 10469 resultMode = MeasureSpec.EXACTLY; 10470 } else if (childDimension == LayoutParams.MATCH_PARENT) { 10471 resultSize = size; 10472 resultMode = parentMode; 10473 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 10474 resultSize = size; 10475 if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { 10476 resultMode = MeasureSpec.AT_MOST; 10477 } else { 10478 resultMode = MeasureSpec.UNSPECIFIED; 10479 } 10480 10481 } 10482 } 10483 //noinspection WrongConstant 10484 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 10485 } 10486 10487 /** 10488 * Returns the measured width of the given child, plus the additional size of 10489 * any insets applied by {@link ItemDecoration ItemDecorations}. 10490 * 10491 * @param child Child view to query 10492 * @return child's measured width plus <code>ItemDecoration</code> insets 10493 * @see View#getMeasuredWidth() 10494 */ getDecoratedMeasuredWidth(@onNull View child)10495 public int getDecoratedMeasuredWidth(@NonNull View child) { 10496 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 10497 return child.getMeasuredWidth() + insets.left + insets.right; 10498 } 10499 10500 /** 10501 * Returns the measured height of the given child, plus the additional size of 10502 * any insets applied by {@link ItemDecoration ItemDecorations}. 10503 * 10504 * @param child Child view to query 10505 * @return child's measured height plus <code>ItemDecoration</code> insets 10506 * @see View#getMeasuredHeight() 10507 */ getDecoratedMeasuredHeight(@onNull View child)10508 public int getDecoratedMeasuredHeight(@NonNull View child) { 10509 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 10510 return child.getMeasuredHeight() + insets.top + insets.bottom; 10511 } 10512 10513 /** 10514 * Lay out the given child view within the RecyclerView using coordinates that 10515 * include any current {@link ItemDecoration ItemDecorations}. 10516 * 10517 * <p>LayoutManagers should prefer working in sizes and coordinates that include 10518 * item decoration insets whenever possible. This allows the LayoutManager to effectively 10519 * ignore decoration insets within measurement and layout code. See the following 10520 * methods:</p> 10521 * <ul> 10522 * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li> 10523 * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li> 10524 * <li>{@link #measureChild(View, int, int)}</li> 10525 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 10526 * <li>{@link #getDecoratedLeft(View)}</li> 10527 * <li>{@link #getDecoratedTop(View)}</li> 10528 * <li>{@link #getDecoratedRight(View)}</li> 10529 * <li>{@link #getDecoratedBottom(View)}</li> 10530 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 10531 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 10532 * </ul> 10533 * 10534 * @param child Child to lay out 10535 * @param left Left edge, with item decoration insets included 10536 * @param top Top edge, with item decoration insets included 10537 * @param right Right edge, with item decoration insets included 10538 * @param bottom Bottom edge, with item decoration insets included 10539 * @see View#layout(int, int, int, int) 10540 * @see #layoutDecoratedWithMargins(View, int, int, int, int) 10541 */ layoutDecorated(@onNull View child, int left, int top, int right, int bottom)10542 public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) { 10543 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 10544 child.layout(left + insets.left, top + insets.top, right - insets.right, 10545 bottom - insets.bottom); 10546 } 10547 10548 /** 10549 * Lay out the given child view within the RecyclerView using coordinates that 10550 * include any current {@link ItemDecoration ItemDecorations} and margins. 10551 * 10552 * <p>LayoutManagers should prefer working in sizes and coordinates that include 10553 * item decoration insets whenever possible. This allows the LayoutManager to effectively 10554 * ignore decoration insets within measurement and layout code. See the following 10555 * methods:</p> 10556 * <ul> 10557 * <li>{@link #layoutDecorated(View, int, int, int, int)}</li> 10558 * <li>{@link #measureChild(View, int, int)}</li> 10559 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 10560 * <li>{@link #getDecoratedLeft(View)}</li> 10561 * <li>{@link #getDecoratedTop(View)}</li> 10562 * <li>{@link #getDecoratedRight(View)}</li> 10563 * <li>{@link #getDecoratedBottom(View)}</li> 10564 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 10565 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 10566 * </ul> 10567 * 10568 * @param child Child to lay out 10569 * @param left Left edge, with item decoration insets and left margin included 10570 * @param top Top edge, with item decoration insets and top margin included 10571 * @param right Right edge, with item decoration insets and right margin included 10572 * @param bottom Bottom edge, with item decoration insets and bottom margin included 10573 * @see View#layout(int, int, int, int) 10574 * @see #layoutDecorated(View, int, int, int, int) 10575 */ layoutDecoratedWithMargins(@onNull View child, int left, int top, int right, int bottom)10576 public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, 10577 int bottom) { 10578 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 10579 final Rect insets = lp.mDecorInsets; 10580 child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, 10581 right - insets.right - lp.rightMargin, 10582 bottom - insets.bottom - lp.bottomMargin); 10583 } 10584 10585 /** 10586 * Calculates the bounding box of the View while taking into account its matrix changes 10587 * (translation, scale etc) with respect to the RecyclerView. 10588 * <p> 10589 * If {@code includeDecorInsets} is {@code true}, they are applied first before applying 10590 * the View's matrix so that the decor offsets also go through the same transformation. 10591 * 10592 * @param child The ItemView whose bounding box should be calculated. 10593 * @param includeDecorInsets True if the decor insets should be included in the bounding box 10594 * @param out The rectangle into which the output will be written. 10595 */ getTransformedBoundingBox(@onNull View child, boolean includeDecorInsets, @NonNull Rect out)10596 public void getTransformedBoundingBox(@NonNull View child, boolean includeDecorInsets, 10597 @NonNull Rect out) { 10598 if (includeDecorInsets) { 10599 Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 10600 out.set(-insets.left, -insets.top, 10601 child.getWidth() + insets.right, child.getHeight() + insets.bottom); 10602 } else { 10603 out.set(0, 0, child.getWidth(), child.getHeight()); 10604 } 10605 10606 if (mRecyclerView != null) { 10607 final Matrix childMatrix = child.getMatrix(); 10608 if (childMatrix != null && !childMatrix.isIdentity()) { 10609 final RectF tempRectF = mRecyclerView.mTempRectF; 10610 tempRectF.set(out); 10611 childMatrix.mapRect(tempRectF); 10612 out.set( 10613 (int) Math.floor(tempRectF.left), 10614 (int) Math.floor(tempRectF.top), 10615 (int) Math.ceil(tempRectF.right), 10616 (int) Math.ceil(tempRectF.bottom) 10617 ); 10618 } 10619 } 10620 out.offset(child.getLeft(), child.getTop()); 10621 } 10622 10623 /** 10624 * Returns the bounds of the view including its decoration and margins. 10625 * 10626 * @param view The view element to check 10627 * @param outBounds A rect that will receive the bounds of the element including its 10628 * decoration and margins. 10629 */ getDecoratedBoundsWithMargins(@onNull View view, @NonNull Rect outBounds)10630 public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { 10631 RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); 10632 } 10633 10634 /** 10635 * Returns the left edge of the given child view within its parent, offset by any applied 10636 * {@link ItemDecoration ItemDecorations}. 10637 * 10638 * @param child Child to query 10639 * @return Child left edge with offsets applied 10640 * @see #getLeftDecorationWidth(View) 10641 */ getDecoratedLeft(@onNull View child)10642 public int getDecoratedLeft(@NonNull View child) { 10643 return child.getLeft() - getLeftDecorationWidth(child); 10644 } 10645 10646 /** 10647 * Returns the top edge of the given child view within its parent, offset by any applied 10648 * {@link ItemDecoration ItemDecorations}. 10649 * 10650 * @param child Child to query 10651 * @return Child top edge with offsets applied 10652 * @see #getTopDecorationHeight(View) 10653 */ getDecoratedTop(@onNull View child)10654 public int getDecoratedTop(@NonNull View child) { 10655 return child.getTop() - getTopDecorationHeight(child); 10656 } 10657 10658 /** 10659 * Returns the right edge of the given child view within its parent, offset by any applied 10660 * {@link ItemDecoration ItemDecorations}. 10661 * 10662 * @param child Child to query 10663 * @return Child right edge with offsets applied 10664 * @see #getRightDecorationWidth(View) 10665 */ getDecoratedRight(@onNull View child)10666 public int getDecoratedRight(@NonNull View child) { 10667 return child.getRight() + getRightDecorationWidth(child); 10668 } 10669 10670 /** 10671 * Returns the bottom edge of the given child view within its parent, offset by any applied 10672 * {@link ItemDecoration ItemDecorations}. 10673 * 10674 * @param child Child to query 10675 * @return Child bottom edge with offsets applied 10676 * @see #getBottomDecorationHeight(View) 10677 */ getDecoratedBottom(@onNull View child)10678 public int getDecoratedBottom(@NonNull View child) { 10679 return child.getBottom() + getBottomDecorationHeight(child); 10680 } 10681 10682 /** 10683 * Calculates the item decor insets applied to the given child and updates the provided 10684 * Rect instance with the inset values. 10685 * <ul> 10686 * <li>The Rect's left is set to the total width of left decorations.</li> 10687 * <li>The Rect's top is set to the total height of top decorations.</li> 10688 * <li>The Rect's right is set to the total width of right decorations.</li> 10689 * <li>The Rect's bottom is set to total height of bottom decorations.</li> 10690 * </ul> 10691 * <p> 10692 * Note that item decorations are automatically calculated when one of the LayoutManager's 10693 * measure child methods is called. If you need to measure the child with custom specs via 10694 * {@link View#measure(int, int)}, you can use this method to get decorations. 10695 * 10696 * @param child The child view whose decorations should be calculated 10697 * @param outRect The Rect to hold result values 10698 */ calculateItemDecorationsForChild(@onNull View child, @NonNull Rect outRect)10699 public void calculateItemDecorationsForChild(@NonNull View child, @NonNull Rect outRect) { 10700 if (mRecyclerView == null) { 10701 outRect.set(0, 0, 0, 0); 10702 return; 10703 } 10704 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 10705 outRect.set(insets); 10706 } 10707 10708 /** 10709 * Returns the total height of item decorations applied to child's top. 10710 * <p> 10711 * Note that this value is not updated until the View is measured or 10712 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 10713 * 10714 * @param child Child to query 10715 * @return The total height of item decorations applied to the child's top. 10716 * @see #getDecoratedTop(View) 10717 * @see #calculateItemDecorationsForChild(View, Rect) 10718 */ getTopDecorationHeight(@onNull View child)10719 public int getTopDecorationHeight(@NonNull View child) { 10720 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; 10721 } 10722 10723 /** 10724 * Returns the total height of item decorations applied to child's bottom. 10725 * <p> 10726 * Note that this value is not updated until the View is measured or 10727 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 10728 * 10729 * @param child Child to query 10730 * @return The total height of item decorations applied to the child's bottom. 10731 * @see #getDecoratedBottom(View) 10732 * @see #calculateItemDecorationsForChild(View, Rect) 10733 */ getBottomDecorationHeight(@onNull View child)10734 public int getBottomDecorationHeight(@NonNull View child) { 10735 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; 10736 } 10737 10738 /** 10739 * Returns the total width of item decorations applied to child's left. 10740 * <p> 10741 * Note that this value is not updated until the View is measured or 10742 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 10743 * 10744 * @param child Child to query 10745 * @return The total width of item decorations applied to the child's left. 10746 * @see #getDecoratedLeft(View) 10747 * @see #calculateItemDecorationsForChild(View, Rect) 10748 */ getLeftDecorationWidth(@onNull View child)10749 public int getLeftDecorationWidth(@NonNull View child) { 10750 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; 10751 } 10752 10753 /** 10754 * Returns the total width of item decorations applied to child's right. 10755 * <p> 10756 * Note that this value is not updated until the View is measured or 10757 * {@link #calculateItemDecorationsForChild(View, Rect)} is called. 10758 * 10759 * @param child Child to query 10760 * @return The total width of item decorations applied to the child's right. 10761 * @see #getDecoratedRight(View) 10762 * @see #calculateItemDecorationsForChild(View, Rect) 10763 */ getRightDecorationWidth(@onNull View child)10764 public int getRightDecorationWidth(@NonNull View child) { 10765 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; 10766 } 10767 10768 /** 10769 * Called when searching for a focusable view in the given direction has failed 10770 * for the current content of the RecyclerView. 10771 * 10772 * <p>This is the LayoutManager's opportunity to populate views in the given direction 10773 * to fulfill the request if it can. The LayoutManager should attach and return 10774 * the view to be focused, if a focusable view in the given direction is found. 10775 * Otherwise, if all the existing (or the newly populated views) are unfocusable, it returns 10776 * the next unfocusable view to become visible on the screen. This unfocusable view is 10777 * typically the first view that's either partially or fully out of RV's padded bounded 10778 * area in the given direction. The default implementation returns null.</p> 10779 * 10780 * @param focused The currently focused view 10781 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 10782 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 10783 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 10784 * or 0 for not applicable 10785 * @param recycler The recycler to use for obtaining views for currently offscreen items 10786 * @param state Transient state of RecyclerView 10787 * @return The chosen view to be focused if a focusable view is found, otherwise an 10788 * unfocusable view to become visible onto the screen, else null. 10789 */ onFocusSearchFailed(@onNull View focused, int direction, @NonNull Recycler recycler, @NonNull State state)10790 public @Nullable View onFocusSearchFailed(@NonNull View focused, int direction, 10791 @NonNull Recycler recycler, @NonNull State state) { 10792 return null; 10793 } 10794 10795 /** 10796 * This method gives a LayoutManager an opportunity to intercept the initial focus search 10797 * before the default behavior of {@link FocusFinder} is used. If this method returns 10798 * null FocusFinder will attempt to find a focusable child view. If it fails 10799 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} 10800 * will be called to give the LayoutManager an opportunity to add new views for items 10801 * that did not have attached views representing them. The LayoutManager should not add 10802 * or remove views from this method. 10803 * 10804 * @param focused The currently focused view 10805 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 10806 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 10807 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 10808 * @return A descendant view to focus or null to fall back to default behavior. 10809 * The default implementation returns null. 10810 */ onInterceptFocusSearch(@onNull View focused, int direction)10811 public @Nullable View onInterceptFocusSearch(@NonNull View focused, int direction) { 10812 return null; 10813 } 10814 10815 /** 10816 * Returns the scroll amount that brings the given rect in child's coordinate system within 10817 * the padded area of RecyclerView. 10818 * 10819 * @param child The direct child making the request. 10820 * @param rect The rectangle in the child's coordinates the child 10821 * wishes to be on the screen. 10822 * @return The array containing the scroll amount in x and y directions that brings the 10823 * given rect into RV's padded area. 10824 */ getChildRectangleOnScreenScrollAmount(View child, Rect rect)10825 private int[] getChildRectangleOnScreenScrollAmount(View child, Rect rect) { 10826 int[] out = new int[2]; 10827 final int parentLeft = getPaddingLeft(); 10828 final int parentTop = getPaddingTop(); 10829 final int parentRight = getWidth() - getPaddingRight(); 10830 final int parentBottom = getHeight() - getPaddingBottom(); 10831 final int childLeft = child.getLeft() + rect.left - child.getScrollX(); 10832 final int childTop = child.getTop() + rect.top - child.getScrollY(); 10833 final int childRight = childLeft + rect.width(); 10834 final int childBottom = childTop + rect.height(); 10835 10836 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 10837 final int offScreenTop = Math.min(0, childTop - parentTop); 10838 final int offScreenRight = Math.max(0, childRight - parentRight); 10839 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 10840 10841 // Favor the "start" layout direction over the end when bringing one side or the other 10842 // of a large rect into view. If we decide to bring in end because start is already 10843 // visible, limit the scroll such that start won't go out of bounds. 10844 final int dx; 10845 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 10846 dx = offScreenRight != 0 ? offScreenRight 10847 : Math.max(offScreenLeft, childRight - parentRight); 10848 } else { 10849 dx = offScreenLeft != 0 ? offScreenLeft 10850 : Math.min(childLeft - parentLeft, offScreenRight); 10851 } 10852 10853 // Favor bringing the top into view over the bottom. If top is already visible and 10854 // we should scroll to make bottom visible, make sure top does not go out of bounds. 10855 final int dy = offScreenTop != 0 ? offScreenTop 10856 : Math.min(childTop - parentTop, offScreenBottom); 10857 out[0] = dx; 10858 out[1] = dy; 10859 return out; 10860 } 10861 10862 /** 10863 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 10864 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 10865 * android.graphics.Rect, boolean)} for more details. 10866 * 10867 * <p>The base implementation will attempt to perform a standard programmatic scroll 10868 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 10869 * 10870 * @param parent The parent RecyclerView. 10871 * @param child The direct child making the request. 10872 * @param rect The rectangle in the child's coordinates the child 10873 * wishes to be on the screen. 10874 * @param immediate True to forbid animated or delayed scrolling, 10875 * false otherwise 10876 * @return Whether the group scrolled to handle the operation 10877 */ requestChildRectangleOnScreen(@onNull RecyclerView parent, @NonNull View child, @NonNull Rect rect, boolean immediate)10878 public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, 10879 @NonNull View child, @NonNull Rect rect, boolean immediate) { 10880 return requestChildRectangleOnScreen(parent, child, rect, immediate, false); 10881 } 10882 10883 /** 10884 * Requests that the given child of the RecyclerView be positioned onto the screen. This 10885 * method can be called for both unfocusable and focusable child views. For unfocusable 10886 * child views, focusedChildVisible is typically true in which case, layout manager 10887 * makes the child view visible only if the currently focused child stays in-bounds of RV. 10888 * 10889 * @param parent The parent RecyclerView. 10890 * @param child The direct child making the request. 10891 * @param rect The rectangle in the child's coordinates the child 10892 * wishes to be on the screen. 10893 * @param immediate True to forbid animated or delayed scrolling, 10894 * false otherwise 10895 * @param focusedChildVisible Whether the currently focused view must stay visible. 10896 * @return Whether the group scrolled to handle the operation 10897 */ requestChildRectangleOnScreen(@onNull RecyclerView parent, @NonNull View child, @NonNull Rect rect, boolean immediate, boolean focusedChildVisible)10898 public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, 10899 @NonNull View child, @NonNull Rect rect, boolean immediate, 10900 boolean focusedChildVisible) { 10901 int[] scrollAmount = getChildRectangleOnScreenScrollAmount(child, rect 10902 ); 10903 int dx = scrollAmount[0]; 10904 int dy = scrollAmount[1]; 10905 if (!focusedChildVisible || isFocusedChildVisibleAfterScrolling(parent, dx, dy)) { 10906 if (dx != 0 || dy != 0) { 10907 if (immediate) { 10908 parent.scrollBy(dx, dy); 10909 } else { 10910 parent.smoothScrollBy(dx, dy); 10911 } 10912 return true; 10913 } 10914 } 10915 return false; 10916 } 10917 10918 /** 10919 * Returns whether the given child view is partially or fully visible within the padded 10920 * bounded area of RecyclerView, depending on the input parameters. 10921 * A view is partially visible if it has non-zero overlap with RV's padded bounded area. 10922 * If acceptEndPointInclusion flag is set to true, it's also considered partially 10923 * visible if it's located outside RV's bounds and it's hitting either RV's start or end 10924 * bounds. 10925 * 10926 * @param child The child view to be examined. 10927 * @param completelyVisible If true, the method returns true if and only if the 10928 * child is 10929 * completely visible. If false, the method returns true 10930 * if and 10931 * only if the child is only partially visible (that is it 10932 * will 10933 * return false if the child is either completely visible 10934 * or out 10935 * of RV's bounds). 10936 * @param acceptEndPointInclusion If the view's endpoint intersection with RV's start of end 10937 * bounds is enough to consider it partially visible, 10938 * false otherwise. 10939 * @return True if the given child is partially or fully visible, false otherwise. 10940 */ isViewPartiallyVisible(@onNull View child, boolean completelyVisible, boolean acceptEndPointInclusion)10941 public boolean isViewPartiallyVisible(@NonNull View child, boolean completelyVisible, 10942 boolean acceptEndPointInclusion) { 10943 int boundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS 10944 | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); 10945 boolean isViewFullyVisible = mHorizontalBoundCheck.isViewWithinBoundFlags(child, 10946 boundsFlag) 10947 && mVerticalBoundCheck.isViewWithinBoundFlags(child, boundsFlag); 10948 if (completelyVisible) { 10949 return isViewFullyVisible; 10950 } else { 10951 return !isViewFullyVisible; 10952 } 10953 } 10954 10955 /** 10956 * Returns whether the currently focused child stays within RV's bounds with the given 10957 * amount of scrolling. 10958 * 10959 * @param parent The parent RecyclerView. 10960 * @param dx The scrolling in x-axis direction to be performed. 10961 * @param dy The scrolling in y-axis direction to be performed. 10962 * @return {@code false} if the focused child is not at least partially visible after 10963 * scrolling or no focused child exists, {@code true} otherwise. 10964 */ isFocusedChildVisibleAfterScrolling(RecyclerView parent, int dx, int dy)10965 private boolean isFocusedChildVisibleAfterScrolling(RecyclerView parent, int dx, int dy) { 10966 final View focusedChild = parent.getFocusedChild(); 10967 if (focusedChild == null) { 10968 return false; 10969 } 10970 final int parentLeft = getPaddingLeft(); 10971 final int parentTop = getPaddingTop(); 10972 final int parentRight = getWidth() - getPaddingRight(); 10973 final int parentBottom = getHeight() - getPaddingBottom(); 10974 final Rect bounds = mRecyclerView.mTempRect; 10975 getDecoratedBoundsWithMargins(focusedChild, bounds); 10976 10977 if (bounds.left - dx >= parentRight || bounds.right - dx <= parentLeft 10978 || bounds.top - dy >= parentBottom || bounds.bottom - dy <= parentTop) { 10979 return false; 10980 } 10981 return true; 10982 } 10983 10984 /** 10985 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} 10986 */ 10987 @Deprecated onRequestChildFocus(@onNull RecyclerView parent, @NonNull View child, @Nullable View focused)10988 public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull View child, 10989 @Nullable View focused) { 10990 // eat the request if we are in the middle of a scroll or layout 10991 return isSmoothScrolling() || parent.isComputingLayout(); 10992 } 10993 10994 /** 10995 * Called when a descendant view of the RecyclerView requests focus. 10996 * 10997 * <p>A LayoutManager wishing to keep focused views aligned in a specific 10998 * portion of the view may implement that behavior in an override of this method.</p> 10999 * 11000 * <p>If the LayoutManager executes different behavior that should override the default 11001 * behavior of scrolling the focused child on screen instead of running alongside it, 11002 * this method should return true.</p> 11003 * 11004 * @param parent The RecyclerView hosting this LayoutManager 11005 * @param state Current state of RecyclerView 11006 * @param child Direct child of the RecyclerView containing the newly focused view 11007 * @param focused The newly focused view. This may be the same view as child or it may be 11008 * null 11009 * @return true if the default scroll behavior should be suppressed 11010 */ onRequestChildFocus(@onNull RecyclerView parent, @NonNull State state, @NonNull View child, @Nullable View focused)11011 public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull State state, 11012 @NonNull View child, @Nullable View focused) { 11013 return onRequestChildFocus(parent, child, focused); 11014 } 11015 11016 /** 11017 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via 11018 * {@link RecyclerView#setAdapter(Adapter)} or 11019 * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this 11020 * opportunity to clear caches and configure state such that it can relayout appropriately 11021 * with the new data and potentially new view types. 11022 * 11023 * <p>The default implementation removes all currently attached views.</p> 11024 * 11025 * @param oldAdapter The previous adapter instance. Will be null if there was previously no 11026 * adapter. 11027 * @param newAdapter The new adapter instance. Might be null if 11028 * {@link RecyclerView#setAdapter(RecyclerView.Adapter)} is called with 11029 * {@code null}. 11030 */ onAdapterChanged(@ullable Adapter oldAdapter, @Nullable Adapter newAdapter)11031 public void onAdapterChanged(@Nullable Adapter oldAdapter, @Nullable Adapter newAdapter) { 11032 } 11033 11034 /** 11035 * Called to populate focusable views within the RecyclerView. 11036 * 11037 * <p>The LayoutManager implementation should return <code>true</code> if the default 11038 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 11039 * suppressed.</p> 11040 * 11041 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 11042 * to fall back to the default ViewGroup behavior.</p> 11043 * 11044 * @param recyclerView The RecyclerView hosting this LayoutManager 11045 * @param views List of output views. This method should add valid focusable views 11046 * to this list. 11047 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 11048 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 11049 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 11050 * @param focusableMode The type of focusables to be added. 11051 * @return true to suppress the default behavior, false to add default focusables after 11052 * this method returns. 11053 * @see #FOCUSABLES_ALL 11054 * @see #FOCUSABLES_TOUCH_MODE 11055 */ onAddFocusables(@onNull RecyclerView recyclerView, @NonNull ArrayList<View> views, int direction, int focusableMode)11056 public boolean onAddFocusables(@NonNull RecyclerView recyclerView, 11057 @NonNull ArrayList<View> views, int direction, int focusableMode) { 11058 return false; 11059 } 11060 11061 /** 11062 * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or 11063 * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire 11064 * data set has changed. 11065 */ onItemsChanged(@onNull RecyclerView recyclerView)11066 public void onItemsChanged(@NonNull RecyclerView recyclerView) { 11067 } 11068 11069 /** 11070 * Called when items have been added to the adapter. The LayoutManager may choose to 11071 * requestLayout if the inserted items would require refreshing the currently visible set 11072 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 11073 */ onItemsAdded(@onNull RecyclerView recyclerView, int positionStart, int itemCount)11074 public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, 11075 int itemCount) { 11076 } 11077 11078 /** 11079 * Called when items have been removed from the adapter. 11080 */ onItemsRemoved(@onNull RecyclerView recyclerView, int positionStart, int itemCount)11081 public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, 11082 int itemCount) { 11083 } 11084 11085 /** 11086 * Called when items have been changed in the adapter. 11087 * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)} 11088 * instead, then this callback will not be invoked. 11089 */ onItemsUpdated(@onNull RecyclerView recyclerView, int positionStart, int itemCount)11090 public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, 11091 int itemCount) { 11092 } 11093 11094 /** 11095 * Called when items have been changed in the adapter and with optional payload. 11096 * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}. 11097 */ onItemsUpdated(@onNull RecyclerView recyclerView, int positionStart, int itemCount, @Nullable Object payload)11098 public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, 11099 int itemCount, @Nullable Object payload) { 11100 onItemsUpdated(recyclerView, positionStart, itemCount); 11101 } 11102 11103 /** 11104 * Called when an item is moved withing the adapter. 11105 * <p> 11106 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE 11107 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} 11108 * is called. 11109 */ onItemsMoved(@onNull RecyclerView recyclerView, int from, int to, int itemCount)11110 public void onItemsMoved(@NonNull RecyclerView recyclerView, int from, int to, 11111 int itemCount) { 11112 11113 } 11114 11115 11116 /** 11117 * <p>Override this method if you want to support scroll bars.</p> 11118 * 11119 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> 11120 * 11121 * <p>Default implementation returns 0.</p> 11122 * 11123 * @param state Current state of RecyclerView 11124 * @return The horizontal extent of the scrollbar's thumb 11125 * @see RecyclerView#computeHorizontalScrollExtent() 11126 */ computeHorizontalScrollExtent(@onNull State state)11127 public int computeHorizontalScrollExtent(@NonNull State state) { 11128 return 0; 11129 } 11130 11131 /** 11132 * <p>Override this method if you want to support scroll bars.</p> 11133 * 11134 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> 11135 * 11136 * <p>Default implementation returns 0.</p> 11137 * 11138 * @param state Current State of RecyclerView where you can find total item count 11139 * @return The horizontal offset of the scrollbar's thumb 11140 * @see RecyclerView#computeHorizontalScrollOffset() 11141 */ computeHorizontalScrollOffset(@onNull State state)11142 public int computeHorizontalScrollOffset(@NonNull State state) { 11143 return 0; 11144 } 11145 11146 /** 11147 * <p>Override this method if you want to support scroll bars.</p> 11148 * 11149 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> 11150 * 11151 * <p>Default implementation returns 0.</p> 11152 * 11153 * @param state Current State of RecyclerView where you can find total item count 11154 * @return The total horizontal range represented by the horizontal scrollbar 11155 * @see RecyclerView#computeHorizontalScrollRange() 11156 */ computeHorizontalScrollRange(@onNull State state)11157 public int computeHorizontalScrollRange(@NonNull State state) { 11158 return 0; 11159 } 11160 11161 /** 11162 * <p>Override this method if you want to support scroll bars.</p> 11163 * 11164 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> 11165 * 11166 * <p>Default implementation returns 0.</p> 11167 * 11168 * @param state Current state of RecyclerView 11169 * @return The vertical extent of the scrollbar's thumb 11170 * @see RecyclerView#computeVerticalScrollExtent() 11171 */ computeVerticalScrollExtent(@onNull State state)11172 public int computeVerticalScrollExtent(@NonNull State state) { 11173 return 0; 11174 } 11175 11176 /** 11177 * <p>Override this method if you want to support scroll bars.</p> 11178 * 11179 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> 11180 * 11181 * <p>Default implementation returns 0.</p> 11182 * 11183 * @param state Current State of RecyclerView where you can find total item count 11184 * @return The vertical offset of the scrollbar's thumb 11185 * @see RecyclerView#computeVerticalScrollOffset() 11186 */ computeVerticalScrollOffset(@onNull State state)11187 public int computeVerticalScrollOffset(@NonNull State state) { 11188 return 0; 11189 } 11190 11191 /** 11192 * <p>Override this method if you want to support scroll bars.</p> 11193 * 11194 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> 11195 * 11196 * <p>Default implementation returns 0.</p> 11197 * 11198 * @param state Current State of RecyclerView where you can find total item count 11199 * @return The total vertical range represented by the vertical scrollbar 11200 * @see RecyclerView#computeVerticalScrollRange() 11201 */ computeVerticalScrollRange(@onNull State state)11202 public int computeVerticalScrollRange(@NonNull State state) { 11203 return 0; 11204 } 11205 11206 /** 11207 * Measure the attached RecyclerView. Implementations must call 11208 * {@link #setMeasuredDimension(int, int)} before returning. 11209 * <p> 11210 * It is strongly advised to use the AutoMeasure mechanism by overriding 11211 * {@link #isAutoMeasureEnabled()} to return true as AutoMeasure handles all the standard 11212 * measure cases including when the RecyclerView's layout_width or layout_height have been 11213 * set to wrap_content. If {@link #isAutoMeasureEnabled()} is overridden to return true, 11214 * this method should not be overridden. 11215 * <p> 11216 * The default implementation will handle EXACTLY measurements and respect 11217 * the minimum width and height properties of the host RecyclerView if measured 11218 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView 11219 * will consume all available space. 11220 * 11221 * @param recycler Recycler 11222 * @param state Transient state of RecyclerView 11223 * @param widthSpec Width {@link android.view.View.MeasureSpec} 11224 * @param heightSpec Height {@link android.view.View.MeasureSpec} 11225 * @see #isAutoMeasureEnabled() 11226 * @see #setMeasuredDimension(int, int) 11227 */ onMeasure(@onNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec)11228 public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, 11229 int heightSpec) { 11230 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); 11231 } 11232 11233 /** 11234 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the 11235 * host RecyclerView. 11236 * 11237 * @param widthSize Measured width 11238 * @param heightSize Measured height 11239 */ setMeasuredDimension(int widthSize, int heightSize)11240 public void setMeasuredDimension(int widthSize, int heightSize) { 11241 mRecyclerView.setMeasuredDimension(widthSize, heightSize); 11242 } 11243 11244 /** 11245 * @return The host RecyclerView's {@link View#getMinimumWidth()} 11246 */ 11247 @Px getMinimumWidth()11248 public int getMinimumWidth() { 11249 return ViewCompat.getMinimumWidth(mRecyclerView); 11250 } 11251 11252 /** 11253 * @return The host RecyclerView's {@link View#getMinimumHeight()} 11254 */ 11255 @Px getMinimumHeight()11256 public int getMinimumHeight() { 11257 return ViewCompat.getMinimumHeight(mRecyclerView); 11258 } 11259 11260 /** 11261 * <p>Called when the LayoutManager should save its state. This is a good time to save your 11262 * scroll position, configuration and anything else that may be required to restore the same 11263 * layout state if the LayoutManager is recreated.</p> 11264 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and 11265 * restore. This will let you share information between your LayoutManagers but it is also 11266 * your responsibility to make sure they use the same parcelable class.</p> 11267 * 11268 * @return Necessary information for LayoutManager to be able to restore its state 11269 */ onSaveInstanceState()11270 public @Nullable Parcelable onSaveInstanceState() { 11271 return null; 11272 } 11273 11274 /** 11275 * Called when the RecyclerView is ready to restore the state based on a previous 11276 * RecyclerView. 11277 * 11278 * Notice that this might happen after an actual layout, based on how Adapter prefers to 11279 * restore State. See {@link Adapter#getStateRestorationPolicy()} for more information. 11280 * 11281 * @param state The parcelable that was returned by the previous LayoutManager's 11282 * {@link #onSaveInstanceState()} method. 11283 */ 11284 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly onRestoreInstanceState(Parcelable state)11285 public void onRestoreInstanceState(Parcelable state) { 11286 11287 } 11288 stopSmoothScroller()11289 void stopSmoothScroller() { 11290 if (mSmoothScroller != null) { 11291 mSmoothScroller.stop(); 11292 } 11293 } 11294 onSmoothScrollerStopped(SmoothScroller smoothScroller)11295 void onSmoothScrollerStopped(SmoothScroller smoothScroller) { 11296 if (mSmoothScroller == smoothScroller) { 11297 mSmoothScroller = null; 11298 } 11299 } 11300 11301 /** 11302 * RecyclerView calls this method to notify LayoutManager that scroll state has changed. 11303 * 11304 * @param state The new scroll state for RecyclerView 11305 */ onScrollStateChanged(int state)11306 public void onScrollStateChanged(int state) { 11307 } 11308 11309 /** 11310 * Removes all views and recycles them using the given recycler. 11311 * <p> 11312 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. 11313 * <p> 11314 * If a View is marked as "ignored", it is not removed nor recycled. 11315 * 11316 * @param recycler Recycler to use to recycle children 11317 * @see #removeAndRecycleView(View, Recycler) 11318 * @see #removeAndRecycleViewAt(int, Recycler) 11319 * @see #ignoreView(View) 11320 */ removeAndRecycleAllViews(@onNull Recycler recycler)11321 public void removeAndRecycleAllViews(@NonNull Recycler recycler) { 11322 for (int i = getChildCount() - 1; i >= 0; i--) { 11323 final View view = getChildAt(i); 11324 if (!getChildViewHolderInt(view).shouldIgnore()) { 11325 removeAndRecycleViewAt(i, recycler); 11326 } 11327 } 11328 } 11329 11330 // called by accessibility delegate onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info)11331 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 11332 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info); 11333 } 11334 11335 /** 11336 * Called by the AccessibilityDelegate when the information about the current layout should 11337 * be populated. 11338 * <p> 11339 * Default implementation adds a {@link 11340 * androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. 11341 * <p> 11342 * You should override 11343 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 11344 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, 11345 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and 11346 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for 11347 * more accurate accessibility information. 11348 * 11349 * @param recycler The Recycler that can be used to convert view positions into adapter 11350 * positions 11351 * @param state The current state of RecyclerView 11352 * @param info The info that should be filled by the LayoutManager 11353 * @see View#onInitializeAccessibilityNodeInfo( 11354 *android.view.accessibility.AccessibilityNodeInfo) 11355 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 11356 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) 11357 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) 11358 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) 11359 */ onInitializeAccessibilityNodeInfo(@onNull Recycler recycler, @NonNull State state, @NonNull AccessibilityNodeInfoCompat info)11360 public void onInitializeAccessibilityNodeInfo(@NonNull Recycler recycler, 11361 @NonNull State state, @NonNull AccessibilityNodeInfoCompat info) { 11362 if (mRecyclerView.canScrollVertically(-1) || mRecyclerView.canScrollHorizontally(-1)) { 11363 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 11364 info.setScrollable(true); 11365 info.setGranularScrollingSupported(true); 11366 } 11367 if (mRecyclerView.canScrollVertically(1) || mRecyclerView.canScrollHorizontally(1)) { 11368 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 11369 info.setScrollable(true); 11370 info.setGranularScrollingSupported(true); 11371 } 11372 final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo = 11373 AccessibilityNodeInfoCompat.CollectionInfoCompat 11374 .obtain(getRowCountForAccessibility(recycler, state), 11375 getColumnCountForAccessibility(recycler, state), 11376 isLayoutHierarchical(recycler, state), 11377 getSelectionModeForAccessibility(recycler, state)); 11378 info.setCollectionInfo(collectionInfo); 11379 } 11380 11381 // called by accessibility delegate onInitializeAccessibilityEvent(@onNull AccessibilityEvent event)11382 public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) { 11383 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); 11384 } 11385 11386 /** 11387 * Called by the accessibility delegate to initialize an accessibility event. 11388 * <p> 11389 * Default implementation adds item count and scroll information to the event. 11390 * 11391 * @param recycler The Recycler that can be used to convert view positions into adapter 11392 * positions 11393 * @param state The current state of RecyclerView 11394 * @param event The event instance to initialize 11395 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) 11396 */ onInitializeAccessibilityEvent(@onNull Recycler recycler, @NonNull State state, @NonNull AccessibilityEvent event)11397 public void onInitializeAccessibilityEvent(@NonNull Recycler recycler, @NonNull State state, 11398 @NonNull AccessibilityEvent event) { 11399 if (mRecyclerView == null || event == null) { 11400 return; 11401 } 11402 event.setScrollable(mRecyclerView.canScrollVertically(1) 11403 || mRecyclerView.canScrollVertically(-1) 11404 || mRecyclerView.canScrollHorizontally(-1) 11405 || mRecyclerView.canScrollHorizontally(1)); 11406 11407 if (mRecyclerView.mAdapter != null) { 11408 event.setItemCount(mRecyclerView.mAdapter.getItemCount()); 11409 } 11410 } 11411 11412 // called by accessibility delegate onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info)11413 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { 11414 final ViewHolder vh = getChildViewHolderInt(host); 11415 // avoid trying to create accessibility node info for removed children 11416 if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) { 11417 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, 11418 mRecyclerView.mState, host, info); 11419 } 11420 } 11421 11422 /** 11423 * Called by the AccessibilityDelegate when the accessibility information for a specific 11424 * item should be populated. 11425 * <p> 11426 * Default implementation adds basic positioning information about the item. 11427 * 11428 * @param recycler The Recycler that can be used to convert view positions into adapter 11429 * positions 11430 * @param state The current state of RecyclerView 11431 * @param host The child for which accessibility node info should be populated 11432 * @param info The info to fill out about the item 11433 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, 11434 * android.view.accessibility.AccessibilityNodeInfo) 11435 */ onInitializeAccessibilityNodeInfoForItem(@onNull Recycler recycler, @NonNull State state, @NonNull View host, @NonNull AccessibilityNodeInfoCompat info)11436 public void onInitializeAccessibilityNodeInfoForItem(@NonNull Recycler recycler, 11437 @NonNull State state, @NonNull View host, 11438 @NonNull AccessibilityNodeInfoCompat info) { 11439 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; 11440 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; 11441 final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo = 11442 AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, 11443 columnIndexGuess, 1, false, false); 11444 info.setCollectionItemInfo(itemInfo); 11445 } 11446 11447 /** 11448 * A LayoutManager can call this method to force RecyclerView to run simple animations in 11449 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data 11450 * change). 11451 * <p> 11452 * Note that, calling this method will not guarantee that RecyclerView will run animations 11453 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will 11454 * not run any animations but will still clear this flag after the layout is complete. 11455 */ requestSimpleAnimationsInNextLayout()11456 public void requestSimpleAnimationsInNextLayout() { 11457 mRequestedSimpleAnimations = true; 11458 } 11459 11460 /** 11461 * Returns the selection mode for accessibility. Should be 11462 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, 11463 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or 11464 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. 11465 * <p> 11466 * Default implementation returns 11467 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 11468 * 11469 * @param recycler The Recycler that can be used to convert view positions into adapter 11470 * positions 11471 * @param state The current state of RecyclerView 11472 * @return Selection mode for accessibility. Default implementation returns 11473 * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. 11474 */ getSelectionModeForAccessibility(@onNull Recycler recycler, @NonNull State state)11475 public int getSelectionModeForAccessibility(@NonNull Recycler recycler, 11476 @NonNull State state) { 11477 return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; 11478 } 11479 11480 /** 11481 * Returns the number of rows for accessibility. 11482 * <p> 11483 * Default implementation returns the number of items in the adapter if LayoutManager 11484 * supports vertical scrolling or 1 if LayoutManager does not support vertical 11485 * scrolling. 11486 * 11487 * @param recycler The Recycler that can be used to convert view positions into adapter 11488 * positions 11489 * @param state The current state of RecyclerView 11490 * @return The number of rows in LayoutManager for accessibility. 11491 */ getRowCountForAccessibility(@onNull Recycler recycler, @NonNull State state)11492 public int getRowCountForAccessibility(@NonNull Recycler recycler, @NonNull State state) { 11493 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 11494 return 1; 11495 } 11496 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; 11497 } 11498 11499 /** 11500 * Returns the number of columns for accessibility. 11501 * <p> 11502 * Default implementation returns the number of items in the adapter if LayoutManager 11503 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal 11504 * scrolling. 11505 * 11506 * @param recycler The Recycler that can be used to convert view positions into adapter 11507 * positions 11508 * @param state The current state of RecyclerView 11509 * @return The number of rows in LayoutManager for accessibility. 11510 */ getColumnCountForAccessibility(@onNull Recycler recycler, @NonNull State state)11511 public int getColumnCountForAccessibility(@NonNull Recycler recycler, 11512 @NonNull State state) { 11513 if (mRecyclerView == null || mRecyclerView.mAdapter == null) { 11514 return 1; 11515 } 11516 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; 11517 } 11518 11519 /** 11520 * Returns whether layout is hierarchical or not to be used for accessibility. 11521 * <p> 11522 * Default implementation returns false. 11523 * 11524 * @param recycler The Recycler that can be used to convert view positions into adapter 11525 * positions 11526 * @param state The current state of RecyclerView 11527 * @return True if layout is hierarchical. 11528 */ isLayoutHierarchical(@onNull Recycler recycler, @NonNull State state)11529 public boolean isLayoutHierarchical(@NonNull Recycler recycler, @NonNull State state) { 11530 return false; 11531 } 11532 11533 // called by accessibility delegate performAccessibilityAction(int action, @Nullable Bundle args)11534 boolean performAccessibilityAction(int action, @Nullable Bundle args) { 11535 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, 11536 action, args); 11537 } 11538 11539 /** 11540 * Called by AccessibilityDelegate when an action is requested from the RecyclerView. 11541 * 11542 * @param recycler The Recycler that can be used to convert view positions into adapter 11543 * positions 11544 * @param state The current state of RecyclerView 11545 * @param action The action to perform 11546 * @param args Optional action arguments 11547 * @see View#performAccessibilityAction(int, android.os.Bundle) 11548 */ performAccessibilityAction(@onNull Recycler recycler, @NonNull State state, int action, @Nullable Bundle args)11549 public boolean performAccessibilityAction(@NonNull Recycler recycler, @NonNull State state, 11550 int action, @Nullable Bundle args) { 11551 if (mRecyclerView == null) { 11552 return false; 11553 } 11554 int vScroll = 0, hScroll = 0; 11555 int height = getHeight(); 11556 int width = getWidth(); 11557 Rect rect = new Rect(); 11558 // Gets the visible rect on the screen except for the rotation or scale cases which 11559 // might affect the result. 11560 if (mRecyclerView.getMatrix().isIdentity() && mRecyclerView.getGlobalVisibleRect( 11561 rect)) { 11562 height = rect.height(); 11563 width = rect.width(); 11564 } 11565 11566 switch (action) { 11567 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 11568 if (mRecyclerView.canScrollVertically(-1)) { 11569 vScroll = -(height - getPaddingTop() - getPaddingBottom()); 11570 } 11571 if (mRecyclerView.canScrollHorizontally(-1)) { 11572 hScroll = -(width - getPaddingLeft() - getPaddingRight()); 11573 } 11574 break; 11575 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 11576 if (mRecyclerView.canScrollVertically(1)) { 11577 vScroll = height - getPaddingTop() - getPaddingBottom(); 11578 } 11579 if (mRecyclerView.canScrollHorizontally(1)) { 11580 hScroll = width - getPaddingLeft() - getPaddingRight(); 11581 } 11582 break; 11583 } 11584 11585 if (vScroll == 0 && hScroll == 0) { 11586 return false; 11587 } 11588 11589 float granularScrollAmount = 1F; // The default value. 11590 11591 if (args != null) { 11592 granularScrollAmount = args.getFloat( 11593 AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT, 1F); 11594 if (granularScrollAmount < 0) { 11595 if (sDebugAssertionsEnabled) { 11596 throw new IllegalArgumentException( 11597 "attempting to use ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT with a " 11598 + "negative value (" + granularScrollAmount + ")"); 11599 } 11600 return false; 11601 } 11602 } 11603 11604 if (Float.compare(granularScrollAmount, Float.POSITIVE_INFINITY) == 0) { 11605 // Assume that the client wants to scroll as far as possible. For 11606 // ACTION_SCROLL_BACKWARD, this means scrolling to the beginning of the collection. 11607 // For ACTION_SCROLL_FORWARD, this means scrolling to the end of the collection. 11608 11609 if (mRecyclerView.mAdapter == null) { 11610 return false; 11611 } 11612 switch (action) { 11613 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: 11614 mRecyclerView.smoothScrollToPosition(0); 11615 break; 11616 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: 11617 mRecyclerView.smoothScrollToPosition( 11618 mRecyclerView.mAdapter.getItemCount() - 1); 11619 break; 11620 } 11621 return true; 11622 } 11623 11624 // No adjustments needed to scroll values if granular scroll amount is 1F, which is 11625 // the default, or 0F, which is undefined. 11626 if (Float.compare(1F, granularScrollAmount) != 0 && Float.compare(0F, 11627 granularScrollAmount) != 0) { 11628 hScroll = (int) (hScroll * granularScrollAmount); 11629 vScroll = (int) (vScroll * granularScrollAmount); 11630 } 11631 11632 mRecyclerView.smoothScrollBy(hScroll, vScroll, null, UNDEFINED_DURATION, true); 11633 return true; 11634 } 11635 11636 // called by accessibility delegate performAccessibilityActionForItem(@onNull View view, int action, @Nullable Bundle args)11637 boolean performAccessibilityActionForItem(@NonNull View view, int action, 11638 @Nullable Bundle args) { 11639 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, 11640 view, action, args); 11641 } 11642 11643 /** 11644 * Called by AccessibilityDelegate when an accessibility action is requested on one of the 11645 * children of LayoutManager. 11646 * <p> 11647 * Default implementation does not do anything. 11648 * 11649 * @param recycler The Recycler that can be used to convert view positions into adapter 11650 * positions 11651 * @param state The current state of RecyclerView 11652 * @param view The child view on which the action is performed 11653 * @param action The action to perform 11654 * @param args Optional action arguments 11655 * @return true if action is handled 11656 * @see View#performAccessibilityAction(int, android.os.Bundle) 11657 */ performAccessibilityActionForItem(@onNull Recycler recycler, @NonNull State state, @NonNull View view, int action, @Nullable Bundle args)11658 public boolean performAccessibilityActionForItem(@NonNull Recycler recycler, 11659 @NonNull State state, @NonNull View view, int action, @Nullable Bundle args) { 11660 return false; 11661 } 11662 11663 /** 11664 * Parse the xml attributes to get the most common properties used by layout managers. 11665 * 11666 * {@link android.R.attr#orientation} 11667 * {@link androidx.recyclerview.R.attr#spanCount} 11668 * {@link androidx.recyclerview.R.attr#reverseLayout} 11669 * {@link androidx.recyclerview.R.attr#stackFromEnd} 11670 * 11671 * @return an object containing the properties as specified in the attrs. 11672 */ getProperties(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)11673 public static Properties getProperties(@NonNull Context context, 11674 @Nullable AttributeSet attrs, 11675 int defStyleAttr, int defStyleRes) { 11676 Properties properties = new Properties(); 11677 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, 11678 defStyleAttr, defStyleRes); 11679 properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation, 11680 DEFAULT_ORIENTATION); 11681 properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1); 11682 properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false); 11683 properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false); 11684 a.recycle(); 11685 return properties; 11686 } 11687 setExactMeasureSpecsFrom(RecyclerView recyclerView)11688 void setExactMeasureSpecsFrom(RecyclerView recyclerView) { 11689 setMeasureSpecs( 11690 MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), 11691 MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) 11692 ); 11693 } 11694 11695 /** 11696 * Internal API to allow LayoutManagers to be measured twice. 11697 * <p> 11698 * This is not public because LayoutManagers should be able to handle their layouts in one 11699 * pass but it is very convenient to make existing LayoutManagers support wrapping content 11700 * when both orientations are undefined. 11701 * <p> 11702 * This API will be removed after default LayoutManagers properly implement wrap content in 11703 * non-scroll orientation. 11704 */ shouldMeasureTwice()11705 boolean shouldMeasureTwice() { 11706 return false; 11707 } 11708 hasFlexibleChildInBothOrientations()11709 boolean hasFlexibleChildInBothOrientations() { 11710 final int childCount = getChildCount(); 11711 for (int i = 0; i < childCount; i++) { 11712 final View child = getChildAt(i); 11713 final ViewGroup.LayoutParams lp = child.getLayoutParams(); 11714 if (lp.width < 0 && lp.height < 0) { 11715 return true; 11716 } 11717 } 11718 return false; 11719 } 11720 11721 /** 11722 * Some general properties that a LayoutManager may want to use. 11723 */ 11724 public static class Properties { 11725 /** {@link android.R.attr#orientation} */ 11726 public int orientation; 11727 /** {@link androidx.recyclerview.R.attr#spanCount} */ 11728 public int spanCount; 11729 /** {@link androidx.recyclerview.R.attr#reverseLayout} */ 11730 public boolean reverseLayout; 11731 /** {@link androidx.recyclerview.R.attr#stackFromEnd} */ 11732 public boolean stackFromEnd; 11733 } 11734 } 11735 11736 /** 11737 * An ItemDecoration allows the application to add a special drawing and layout offset 11738 * to specific item views from the adapter's data set. This can be useful for drawing dividers 11739 * between items, highlights, visual grouping boundaries and more. 11740 * 11741 * <p>All ItemDecorations are drawn in the order they were added, before the item 11742 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} 11743 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, 11744 * RecyclerView.State)}.</p> 11745 */ 11746 public abstract static class ItemDecoration { 11747 /** 11748 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 11749 * Any content drawn by this method will be drawn before the item views are drawn, 11750 * and will thus appear underneath the views. 11751 * 11752 * @param c Canvas to draw into 11753 * @param parent RecyclerView this ItemDecoration is drawing into 11754 * @param state The current state of RecyclerView 11755 */ onDraw(@onNull Canvas c, @NonNull RecyclerView parent, @NonNull State state)11756 public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) { 11757 onDraw(c, parent); 11758 } 11759 11760 /** 11761 * @deprecated Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} 11762 */ 11763 @Deprecated onDraw(@onNull Canvas c, @NonNull RecyclerView parent)11764 public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) { 11765 } 11766 11767 /** 11768 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 11769 * Any content drawn by this method will be drawn after the item views are drawn 11770 * and will thus appear over the views. 11771 * 11772 * @param c Canvas to draw into 11773 * @param parent RecyclerView this ItemDecoration is drawing into 11774 * @param state The current state of RecyclerView. 11775 */ onDrawOver(@onNull Canvas c, @NonNull RecyclerView parent, @NonNull State state)11776 public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, 11777 @NonNull State state) { 11778 onDrawOver(c, parent); 11779 } 11780 11781 /** 11782 * @deprecated Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} 11783 */ 11784 @Deprecated onDrawOver(@onNull Canvas c, @NonNull RecyclerView parent)11785 public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) { 11786 } 11787 11788 11789 /** 11790 * @deprecated Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} 11791 */ 11792 @Deprecated getItemOffsets(@onNull Rect outRect, int itemPosition, @NonNull RecyclerView parent)11793 public void getItemOffsets(@NonNull Rect outRect, int itemPosition, 11794 @NonNull RecyclerView parent) { 11795 outRect.set(0, 0, 0, 0); 11796 } 11797 11798 /** 11799 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 11800 * the number of pixels that the item view should be inset by, similar to padding or margin. 11801 * The default implementation sets the bounds of outRect to 0 and returns. 11802 * 11803 * <p> 11804 * If this ItemDecoration does not affect the positioning of item views, it should set 11805 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 11806 * before returning. 11807 * 11808 * <p> 11809 * If you need to access Adapter for additional data, you can call 11810 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the 11811 * View. 11812 * 11813 * @param outRect Rect to receive the output. 11814 * @param view The child view to decorate 11815 * @param parent RecyclerView this ItemDecoration is decorating 11816 * @param state The current state of RecyclerView. 11817 */ getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull State state)11818 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 11819 @NonNull RecyclerView parent, @NonNull State state) { 11820 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), 11821 parent); 11822 } 11823 } 11824 11825 /** 11826 * An OnItemTouchListener allows the application to intercept touch events in progress at the 11827 * view hierarchy level of the RecyclerView before those touch events are considered for 11828 * RecyclerView's own scrolling behavior. 11829 * 11830 * <p>This can be useful for applications that wish to implement various forms of gestural 11831 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 11832 * a touch interaction already in progress even if the RecyclerView is already handling that 11833 * gesture stream itself for the purposes of scrolling.</p> 11834 * 11835 * @see SimpleOnItemTouchListener 11836 */ 11837 public interface OnItemTouchListener { 11838 /** 11839 * Silently observe and/or take over touch events sent to the RecyclerView 11840 * before they are handled by either the RecyclerView itself or its child views. 11841 * 11842 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 11843 * in the order in which each listener was added, before any other touch processing 11844 * by the RecyclerView itself or child views occurs.</p> 11845 * 11846 * @param rv The RecyclerView whose scroll state has changed. 11847 * @param e MotionEvent describing the touch event. All coordinates are in 11848 * the RecyclerView's coordinate system. 11849 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 11850 * to continue with the current behavior and continue observing future events in 11851 * the gesture. 11852 */ onInterceptTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)11853 boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 11854 11855 /** 11856 * Process a touch event as part of a gesture that was claimed by returning true from 11857 * a previous call to {@link #onInterceptTouchEvent}. 11858 * 11859 * @param rv The RecyclerView whose scroll state has changed. 11860 * @param e MotionEvent describing the touch event. All coordinates are in 11861 * the RecyclerView's coordinate system. 11862 */ onTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)11863 void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); 11864 11865 /** 11866 * Called when a child of RecyclerView does not want RecyclerView and its ancestors to 11867 * intercept touch events with 11868 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. 11869 * 11870 * @param disallowIntercept True if the child does not want the parent to 11871 * intercept touch events. 11872 * @see ViewParent#requestDisallowInterceptTouchEvent(boolean) 11873 */ onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)11874 void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); 11875 } 11876 11877 /** 11878 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies 11879 * and default return values. 11880 * <p> 11881 * You may prefer to extend this class if you don't need to override all methods. Another 11882 * benefit of using this class is future compatibility. As the interface may change, we'll 11883 * always provide a default implementation on this class so that your code won't break when 11884 * you update to a new version of the support library. 11885 */ 11886 public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener { 11887 /** {@inheritDoc} */ 11888 @Override onInterceptTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)11889 public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 11890 return false; 11891 } 11892 11893 /** {@inheritDoc} */ 11894 @Override onTouchEvent(@onNull RecyclerView rv, @NonNull MotionEvent e)11895 public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { 11896 } 11897 11898 /** {@inheritDoc} */ 11899 @Override onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)11900 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 11901 } 11902 } 11903 11904 11905 /** 11906 * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event 11907 * has occurred on that RecyclerView. 11908 * <p> 11909 * 11910 * @see RecyclerView#addOnScrollListener(OnScrollListener) 11911 * @see RecyclerView#clearOnChildAttachStateChangeListeners() 11912 */ 11913 public abstract static class OnScrollListener { 11914 /** 11915 * Callback method to be invoked when RecyclerView's scroll state changes. 11916 * 11917 * @param recyclerView The RecyclerView whose scroll state has changed. 11918 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, 11919 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 11920 */ onScrollStateChanged(@onNull RecyclerView recyclerView, int newState)11921 public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 11922 } 11923 11924 /** 11925 * Callback method to be invoked when the RecyclerView has been scrolled. This will be 11926 * called after the scroll has completed. 11927 * <p> 11928 * This callback will also be called if visible item range changes after a layout 11929 * calculation. In that case, dx and dy will be 0. 11930 * 11931 * @param recyclerView The RecyclerView which scrolled. 11932 * @param dx The amount of horizontal scroll. 11933 * @param dy The amount of vertical scroll. 11934 */ onScrolled(@onNull RecyclerView recyclerView, int dx, int dy)11935 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 11936 } 11937 } 11938 11939 /** 11940 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 11941 * a view is recycled. 11942 * 11943 * @see RecyclerView#setRecyclerListener(RecyclerListener) 11944 */ 11945 public interface RecyclerListener { 11946 11947 /** 11948 * This method is called whenever the view in the ViewHolder is recycled. 11949 * 11950 * RecyclerView calls this method right before clearing ViewHolder's internal data and 11951 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information 11952 * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get 11953 * its adapter position. 11954 * 11955 * @param holder The ViewHolder containing the view that was recycled 11956 */ onViewRecycled(@onNull ViewHolder holder)11957 void onViewRecycled(@NonNull ViewHolder holder); 11958 } 11959 11960 /** 11961 * A Listener interface that can be attached to a RecylcerView to get notified 11962 * whenever a ViewHolder is attached to or detached from RecyclerView. 11963 */ 11964 public interface OnChildAttachStateChangeListener { 11965 11966 /** 11967 * Called when a view is attached to the RecyclerView. 11968 * 11969 * @param view The View which is attached to the RecyclerView 11970 */ onChildViewAttachedToWindow(@onNull View view)11971 void onChildViewAttachedToWindow(@NonNull View view); 11972 11973 /** 11974 * Called when a view is detached from RecyclerView. 11975 * 11976 * @param view The View which is being detached from the RecyclerView 11977 */ onChildViewDetachedFromWindow(@onNull View view)11978 void onChildViewDetachedFromWindow(@NonNull View view); 11979 } 11980 11981 /** 11982 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 11983 * 11984 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 11985 * potentially expensive {@link View#findViewById(int)} results.</p> 11986 * 11987 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 11988 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 11989 * their own custom ViewHolder implementations to store data that makes binding view contents 11990 * easier. Implementations should assume that individual item views will hold strong references 11991 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 11992 * strong references to extra off-screen item views for caching purposes</p> 11993 */ 11994 public abstract static class ViewHolder { 11995 public final @NonNull View itemView; 11996 WeakReference<RecyclerView> mNestedRecyclerView; 11997 int mPosition = NO_POSITION; 11998 int mOldPosition = NO_POSITION; 11999 long mItemId = NO_ID; 12000 int mItemViewType = INVALID_TYPE; 12001 int mPreLayoutPosition = NO_POSITION; 12002 12003 // The item that this holder is shadowing during an item change event/animation 12004 ViewHolder mShadowedHolder = null; 12005 // The item that is shadowing this holder during an item change event/animation 12006 ViewHolder mShadowingHolder = null; 12007 12008 /** 12009 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 12010 * are all valid. 12011 */ 12012 static final int FLAG_BOUND = 1 << 0; 12013 12014 /** 12015 * The data this ViewHolder's view reflects is stale and needs to be rebound 12016 * by the adapter. mPosition and mItemId are consistent. 12017 */ 12018 static final int FLAG_UPDATE = 1 << 1; 12019 12020 /** 12021 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 12022 * are not to be trusted and may no longer match the item view type. 12023 * This ViewHolder must be fully rebound to different data. 12024 */ 12025 static final int FLAG_INVALID = 1 << 2; 12026 12027 /** 12028 * This ViewHolder points at data that represents an item previously removed from the 12029 * data set. Its view may still be used for things like outgoing animations. 12030 */ 12031 static final int FLAG_REMOVED = 1 << 3; 12032 12033 /** 12034 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 12035 * and is intended to keep views around during animations. 12036 */ 12037 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 12038 12039 /** 12040 * This ViewHolder is returned from scrap which means we are expecting an addView call 12041 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until 12042 * the end of the layout pass and then recycled by RecyclerView if it is not added back to 12043 * the RecyclerView. 12044 */ 12045 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; 12046 12047 /** 12048 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove 12049 * it unless LayoutManager is replaced. 12050 * It is still fully visible to the LayoutManager. 12051 */ 12052 static final int FLAG_IGNORE = 1 << 7; 12053 12054 /** 12055 * When the View is detached form the parent, we set this flag so that we can take correct 12056 * action when we need to remove it or add it back. 12057 */ 12058 static final int FLAG_TMP_DETACHED = 1 << 8; 12059 12060 /** 12061 * Set when we can no longer determine the adapter position of this ViewHolder until it is 12062 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is 12063 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon 12064 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is 12065 * re-calculated. 12066 */ 12067 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; 12068 12069 /** 12070 * Set when a addChangePayload(null) is called 12071 */ 12072 static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; 12073 12074 /** 12075 * Used by ItemAnimator when a ViewHolder's position changes 12076 */ 12077 static final int FLAG_MOVED = 1 << 11; 12078 12079 /** 12080 * Used by ItemAnimator when a ViewHolder appears in pre-layout 12081 */ 12082 static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; 12083 12084 static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; 12085 12086 /** 12087 * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from 12088 * hidden list (as if it was scrap) without being recycled in between. 12089 * 12090 * When a ViewHolder is hidden, there are 2 paths it can be re-used: 12091 * a) Animation ends, view is recycled and used from the recycle pool. 12092 * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. 12093 * 12094 * This flag is used to represent "case b" where the ViewHolder is reused without being 12095 * recycled (thus "bounced" from the hidden list). This state requires special handling 12096 * because the ViewHolder must be added to pre layout maps for animations as if it was 12097 * already there. 12098 */ 12099 static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; 12100 12101 int mFlags; 12102 12103 private static final List<Object> FULLUPDATE_PAYLOADS = Collections.emptyList(); 12104 12105 List<Object> mPayloads = null; 12106 List<Object> mUnmodifiedPayloads = null; 12107 12108 private int mIsRecyclableCount = 0; 12109 12110 // If non-null, view is currently considered scrap and may be reused for other data by the 12111 // scrap container. 12112 Recycler mScrapContainer = null; 12113 // Keeps whether this ViewHolder lives in Change scrap or Attached scrap 12114 boolean mInChangeScrap = false; 12115 12116 // Saves isImportantForAccessibility value for the view item while it's in hidden state and 12117 // marked as unimportant for accessibility. 12118 private int mWasImportantForAccessibilityBeforeHidden = 12119 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 12120 // set if we defer the accessibility state change of the view holder 12121 @VisibleForTesting 12122 int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 12123 12124 /** 12125 * Is set when VH is bound from the adapter and cleaned right before it is sent to 12126 * {@link RecycledViewPool}. 12127 */ 12128 RecyclerView mOwnerRecyclerView; 12129 12130 // The last adapter that bound this ViewHolder. It is cleaned before VH is recycled. 12131 Adapter<? extends ViewHolder> mBindingAdapter; 12132 ViewHolder(@onNull View itemView)12133 public ViewHolder(@NonNull View itemView) { 12134 if (itemView == null) { 12135 throw new IllegalArgumentException("itemView may not be null"); 12136 } 12137 this.itemView = itemView; 12138 } 12139 flagRemovedAndOffsetPosition(int newPosition, int offset, boolean applyToPreLayout)12140 void flagRemovedAndOffsetPosition(int newPosition, int offset, boolean applyToPreLayout) { 12141 addFlags(ViewHolder.FLAG_REMOVED); 12142 offsetPosition(offset, applyToPreLayout); 12143 mPosition = newPosition; 12144 } 12145 offsetPosition(int offset, boolean applyToPreLayout)12146 void offsetPosition(int offset, boolean applyToPreLayout) { 12147 if (mOldPosition == NO_POSITION) { 12148 mOldPosition = mPosition; 12149 } 12150 if (mPreLayoutPosition == NO_POSITION) { 12151 mPreLayoutPosition = mPosition; 12152 } 12153 if (applyToPreLayout) { 12154 mPreLayoutPosition += offset; 12155 } 12156 mPosition += offset; 12157 if (itemView.getLayoutParams() != null) { 12158 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; 12159 } 12160 } 12161 clearOldPosition()12162 void clearOldPosition() { 12163 mOldPosition = NO_POSITION; 12164 mPreLayoutPosition = NO_POSITION; 12165 } 12166 saveOldPosition()12167 void saveOldPosition() { 12168 if (mOldPosition == NO_POSITION) { 12169 mOldPosition = mPosition; 12170 } 12171 } 12172 shouldIgnore()12173 boolean shouldIgnore() { 12174 return (mFlags & FLAG_IGNORE) != 0; 12175 } 12176 12177 /** 12178 * @see #getLayoutPosition() 12179 * @see #getBindingAdapterPosition() 12180 * @see #getAbsoluteAdapterPosition() 12181 * @deprecated This method is deprecated because its meaning is ambiguous due to the async 12182 * handling of adapter updates. You should use {@link #getLayoutPosition()}, 12183 * {@link #getBindingAdapterPosition()} or {@link #getAbsoluteAdapterPosition()} 12184 * depending on your use case. 12185 */ 12186 @Deprecated getPosition()12187 public final int getPosition() { 12188 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 12189 } 12190 12191 /** 12192 * Returns the position of the ViewHolder in terms of the latest layout pass. 12193 * <p> 12194 * This position is mostly used by RecyclerView components to be consistent while 12195 * RecyclerView lazily processes adapter updates. 12196 * <p> 12197 * For performance and animation reasons, RecyclerView batches all adapter updates until the 12198 * next layout pass. This may cause mismatches between the Adapter position of the item and 12199 * the position it had in the latest layout calculations. 12200 * <p> 12201 * LayoutManagers should always call this method while doing calculations based on item 12202 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, 12203 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position 12204 * of the item. 12205 * <p> 12206 * If LayoutManager needs to call an external method that requires the adapter position of 12207 * the item, it can use {@link #getAbsoluteAdapterPosition()} or 12208 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. 12209 * 12210 * @return Returns the adapter position of the ViewHolder in the latest layout pass. 12211 * @see #getBindingAdapterPosition() 12212 * @see #getAbsoluteAdapterPosition() 12213 */ getLayoutPosition()12214 public final int getLayoutPosition() { 12215 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; 12216 } 12217 12218 12219 /** 12220 * @return {@link #getBindingAdapterPosition()} 12221 * @deprecated This method is confusing when adapters nest other adapters. 12222 * If you are calling this in the context of an Adapter, you probably want to call 12223 * {@link #getBindingAdapterPosition()} or if you want the position as {@link RecyclerView} 12224 * sees it, you should call {@link #getAbsoluteAdapterPosition()}. 12225 */ 12226 @Deprecated getAdapterPosition()12227 public final int getAdapterPosition() { 12228 return getBindingAdapterPosition(); 12229 } 12230 12231 /** 12232 * Returns the Adapter position of the item represented by this ViewHolder with respect to 12233 * the {@link Adapter} that bound it. 12234 * <p> 12235 * Note that this might be different than the {@link #getLayoutPosition()} if there are 12236 * pending adapter updates but a new layout pass has not happened yet. 12237 * <p> 12238 * RecyclerView does not handle any adapter updates until the next layout traversal. This 12239 * may create temporary inconsistencies between what user sees on the screen and what 12240 * adapter contents have. This inconsistency is not important since it will be less than 12241 * 16ms but it might be a problem if you want to use ViewHolder position to access the 12242 * adapter. Sometimes, you may need to get the exact adapter position to do 12243 * some actions in response to user events. In that case, you should use this method which 12244 * will calculate the Adapter position of the ViewHolder. 12245 * <p> 12246 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 12247 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 12248 * <p> 12249 * If the {@link Adapter} that bound this {@link ViewHolder} is inside another 12250 * {@link Adapter} (e.g. {@link ConcatAdapter}), this position might be different than 12251 * {@link #getAbsoluteAdapterPosition()}. If you would like to know the position that 12252 * {@link RecyclerView} considers (e.g. for saved state), you should use 12253 * {@link #getAbsoluteAdapterPosition()}. 12254 * 12255 * @return The adapter position of the item if it still exists in the adapter. 12256 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 12257 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 12258 * layout pass or the ViewHolder has already been recycled. 12259 * @see #getAbsoluteAdapterPosition() 12260 * @see #getLayoutPosition() 12261 */ getBindingAdapterPosition()12262 public final int getBindingAdapterPosition() { 12263 if (mBindingAdapter == null) { 12264 return NO_POSITION; 12265 } 12266 if (mOwnerRecyclerView == null) { 12267 return NO_POSITION; 12268 } 12269 @SuppressWarnings("unchecked") 12270 Adapter<? extends ViewHolder> rvAdapter = mOwnerRecyclerView.getAdapter(); 12271 if (rvAdapter == null) { 12272 return NO_POSITION; 12273 } 12274 int globalPosition = mOwnerRecyclerView.getAdapterPositionInRecyclerView(this); 12275 if (globalPosition == NO_POSITION) { 12276 return NO_POSITION; 12277 } 12278 return rvAdapter.findRelativeAdapterPositionIn(mBindingAdapter, this, globalPosition); 12279 } 12280 12281 /** 12282 * Returns the Adapter position of the item represented by this ViewHolder with respect to 12283 * the {@link RecyclerView}'s {@link Adapter}. If the {@link Adapter} that bound this 12284 * {@link ViewHolder} is inside another adapter (e.g. {@link ConcatAdapter}), this 12285 * position might be different and will include 12286 * the offsets caused by other adapters in the {@link ConcatAdapter}. 12287 * <p> 12288 * Note that this might be different than the {@link #getLayoutPosition()} if there are 12289 * pending adapter updates but a new layout pass has not happened yet. 12290 * <p> 12291 * RecyclerView does not handle any adapter updates until the next layout traversal. This 12292 * may create temporary inconsistencies between what user sees on the screen and what 12293 * adapter contents have. This inconsistency is not important since it will be less than 12294 * 16ms but it might be a problem if you want to use ViewHolder position to access the 12295 * adapter. Sometimes, you may need to get the exact adapter position to do 12296 * some actions in response to user events. In that case, you should use this method which 12297 * will calculate the Adapter position of the ViewHolder. 12298 * <p> 12299 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the 12300 * next layout pass, the return value of this method will be {@link #NO_POSITION}. 12301 * <p> 12302 * Note that if you are querying the position as {@link RecyclerView} sees, you should use 12303 * {@link #getAbsoluteAdapterPosition()} (e.g. you want to use it to save scroll 12304 * state). If you are querying the position to access the {@link Adapter} contents, 12305 * you should use {@link #getBindingAdapterPosition()}. 12306 * 12307 * @return The adapter position of the item from {@link RecyclerView}'s perspective if it 12308 * still exists in the adapter and bound to a valid item. 12309 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, 12310 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last 12311 * layout pass or the ViewHolder has already been recycled. 12312 * @see #getBindingAdapterPosition() 12313 * @see #getLayoutPosition() 12314 */ getAbsoluteAdapterPosition()12315 public final int getAbsoluteAdapterPosition() { 12316 if (mOwnerRecyclerView == null) { 12317 return NO_POSITION; 12318 } 12319 return mOwnerRecyclerView.getAdapterPositionInRecyclerView(this); 12320 } 12321 12322 /** 12323 * Returns the {@link Adapter} that last bound this {@link ViewHolder}. 12324 * Might return {@code null} if this {@link ViewHolder} is not bound to any adapter. 12325 * 12326 * @return The {@link Adapter} that last bound this {@link ViewHolder} or {@code null} if 12327 * this {@link ViewHolder} is not bound by any adapter (e.g. recycled). 12328 */ getBindingAdapter()12329 public final @Nullable Adapter<? extends ViewHolder> getBindingAdapter() { 12330 return mBindingAdapter; 12331 } 12332 12333 /** 12334 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders 12335 * to perform animations. 12336 * <p> 12337 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its 12338 * adapter index in the previous layout. 12339 * 12340 * @return The previous adapter index of the Item represented by this ViewHolder or 12341 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is 12342 * complete). 12343 */ getOldPosition()12344 public final int getOldPosition() { 12345 return mOldPosition; 12346 } 12347 12348 /** 12349 * Returns The itemId represented by this ViewHolder. 12350 * 12351 * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} 12352 * otherwise 12353 */ getItemId()12354 public final long getItemId() { 12355 return mItemId; 12356 } 12357 12358 /** 12359 * @return The view type of this ViewHolder. 12360 */ getItemViewType()12361 public final int getItemViewType() { 12362 return mItemViewType; 12363 } 12364 isScrap()12365 boolean isScrap() { 12366 return mScrapContainer != null; 12367 } 12368 unScrap()12369 void unScrap() { 12370 mScrapContainer.unscrapView(this); 12371 } 12372 wasReturnedFromScrap()12373 boolean wasReturnedFromScrap() { 12374 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; 12375 } 12376 clearReturnedFromScrapFlag()12377 void clearReturnedFromScrapFlag() { 12378 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; 12379 } 12380 clearTmpDetachFlag()12381 void clearTmpDetachFlag() { 12382 mFlags = mFlags & ~FLAG_TMP_DETACHED; 12383 } 12384 stopIgnoring()12385 void stopIgnoring() { 12386 mFlags = mFlags & ~FLAG_IGNORE; 12387 } 12388 setScrapContainer(Recycler recycler, boolean isChangeScrap)12389 void setScrapContainer(Recycler recycler, boolean isChangeScrap) { 12390 mScrapContainer = recycler; 12391 mInChangeScrap = isChangeScrap; 12392 } 12393 isInvalid()12394 boolean isInvalid() { 12395 return (mFlags & FLAG_INVALID) != 0; 12396 } 12397 needsUpdate()12398 boolean needsUpdate() { 12399 return (mFlags & FLAG_UPDATE) != 0; 12400 } 12401 isBound()12402 boolean isBound() { 12403 return (mFlags & FLAG_BOUND) != 0; 12404 } 12405 isRemoved()12406 boolean isRemoved() { 12407 return (mFlags & FLAG_REMOVED) != 0; 12408 } 12409 hasAnyOfTheFlags(int flags)12410 boolean hasAnyOfTheFlags(int flags) { 12411 return (mFlags & flags) != 0; 12412 } 12413 isTmpDetached()12414 boolean isTmpDetached() { 12415 return (mFlags & FLAG_TMP_DETACHED) != 0; 12416 } 12417 isAttachedToTransitionOverlay()12418 boolean isAttachedToTransitionOverlay() { 12419 return itemView.getParent() != null && itemView.getParent() != mOwnerRecyclerView; 12420 } 12421 isAdapterPositionUnknown()12422 boolean isAdapterPositionUnknown() { 12423 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); 12424 } 12425 setFlags(int flags, int mask)12426 void setFlags(int flags, int mask) { 12427 mFlags = (mFlags & ~mask) | (flags & mask); 12428 } 12429 addFlags(int flags)12430 void addFlags(int flags) { 12431 mFlags |= flags; 12432 } 12433 addChangePayload(Object payload)12434 void addChangePayload(Object payload) { 12435 if (payload == null) { 12436 addFlags(FLAG_ADAPTER_FULLUPDATE); 12437 } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 12438 createPayloadsIfNeeded(); 12439 mPayloads.add(payload); 12440 } 12441 } 12442 createPayloadsIfNeeded()12443 private void createPayloadsIfNeeded() { 12444 if (mPayloads == null) { 12445 mPayloads = new ArrayList<Object>(); 12446 mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); 12447 } 12448 } 12449 clearPayload()12450 void clearPayload() { 12451 if (mPayloads != null) { 12452 mPayloads.clear(); 12453 } 12454 mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE; 12455 } 12456 getUnmodifiedPayloads()12457 List<Object> getUnmodifiedPayloads() { 12458 if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { 12459 if (mPayloads == null || mPayloads.size() == 0) { 12460 // Initial state, no update being called. 12461 return FULLUPDATE_PAYLOADS; 12462 } 12463 // there are none-null payloads 12464 return mUnmodifiedPayloads; 12465 } else { 12466 // a full update has been called. 12467 return FULLUPDATE_PAYLOADS; 12468 } 12469 } 12470 resetInternal()12471 void resetInternal() { 12472 if (sDebugAssertionsEnabled && isTmpDetached()) { 12473 throw new IllegalStateException("Attempting to reset temp-detached ViewHolder: " 12474 + this + ". ViewHolders should be fully detached before resetting."); 12475 } 12476 12477 mFlags = 0; 12478 mPosition = NO_POSITION; 12479 mOldPosition = NO_POSITION; 12480 mItemId = NO_ID; 12481 mPreLayoutPosition = NO_POSITION; 12482 mIsRecyclableCount = 0; 12483 mShadowedHolder = null; 12484 mShadowingHolder = null; 12485 clearPayload(); 12486 mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 12487 mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; 12488 clearNestedRecyclerViewIfNotNested(this); 12489 } 12490 12491 /** 12492 * Called when the child view enters the hidden state 12493 */ onEnteredHiddenState(RecyclerView parent)12494 void onEnteredHiddenState(RecyclerView parent) { 12495 // While the view item is in hidden state, make it invisible for the accessibility. 12496 if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) { 12497 mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState; 12498 } else { 12499 mWasImportantForAccessibilityBeforeHidden = 12500 itemView.getImportantForAccessibility(); 12501 } 12502 parent.setChildImportantForAccessibilityInternal(this, 12503 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 12504 } 12505 12506 /** 12507 * Called when the child view leaves the hidden state 12508 */ onLeftHiddenState(RecyclerView parent)12509 void onLeftHiddenState(RecyclerView parent) { 12510 parent.setChildImportantForAccessibilityInternal(this, 12511 mWasImportantForAccessibilityBeforeHidden); 12512 mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 12513 } 12514 12515 @Override toString()12516 public String toString() { 12517 String className = 12518 getClass().isAnonymousClass() ? "ViewHolder" : getClass().getSimpleName(); 12519 final StringBuilder sb = new StringBuilder(className + "{" 12520 + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId 12521 + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); 12522 if (isScrap()) { 12523 sb.append(" scrap ") 12524 .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); 12525 } 12526 if (isInvalid()) sb.append(" invalid"); 12527 if (!isBound()) sb.append(" unbound"); 12528 if (needsUpdate()) sb.append(" update"); 12529 if (isRemoved()) sb.append(" removed"); 12530 if (shouldIgnore()) sb.append(" ignored"); 12531 if (isTmpDetached()) sb.append(" tmpDetached"); 12532 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); 12533 if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); 12534 12535 if (itemView.getParent() == null) sb.append(" no parent"); 12536 sb.append("}"); 12537 return sb.toString(); 12538 } 12539 12540 /** 12541 * Informs the recycler whether this item can be recycled. Views which are not 12542 * recyclable will not be reused for other items until setIsRecyclable() is 12543 * later set to true. Calls to setIsRecyclable() should always be paired (one 12544 * call to setIsRecyclabe(false) should always be matched with a later call to 12545 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally 12546 * reference-counted. 12547 * 12548 * @param recyclable Whether this item is available to be recycled. Default value 12549 * is true. 12550 * @see #isRecyclable() 12551 */ setIsRecyclable(boolean recyclable)12552 public final void setIsRecyclable(boolean recyclable) { 12553 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; 12554 if (mIsRecyclableCount < 0) { 12555 mIsRecyclableCount = 0; 12556 if (sDebugAssertionsEnabled) { 12557 throw new RuntimeException("isRecyclable decremented below 0: " 12558 + "unmatched pair of setIsRecyable() calls for " + this); 12559 } 12560 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " 12561 + "unmatched pair of setIsRecyable() calls for " + this); 12562 } else if (!recyclable && mIsRecyclableCount == 1) { 12563 mFlags |= FLAG_NOT_RECYCLABLE; 12564 } else if (recyclable && mIsRecyclableCount == 0) { 12565 mFlags &= ~FLAG_NOT_RECYCLABLE; 12566 } 12567 if (sVerboseLoggingEnabled) { 12568 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); 12569 } 12570 } 12571 12572 /** 12573 * @return true if this item is available to be recycled, false otherwise. 12574 * @see #setIsRecyclable(boolean) 12575 */ isRecyclable()12576 public final boolean isRecyclable() { 12577 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 12578 && !ViewCompat.hasTransientState(itemView); 12579 } 12580 12581 /** 12582 * Returns whether we have animations referring to this view holder or not. 12583 * This is similar to isRecyclable flag but does not check transient state. 12584 */ shouldBeKeptAsChild()12585 boolean shouldBeKeptAsChild() { 12586 return (mFlags & FLAG_NOT_RECYCLABLE) != 0; 12587 } 12588 12589 /** 12590 * @return True if ViewHolder is not referenced by RecyclerView animations but has 12591 * transient state which will prevent it from being recycled. 12592 */ doesTransientStatePreventRecycling()12593 boolean doesTransientStatePreventRecycling() { 12594 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); 12595 } 12596 isUpdated()12597 boolean isUpdated() { 12598 return (mFlags & FLAG_UPDATE) != 0; 12599 } 12600 } 12601 12602 /** 12603 * This method is here so that we can control the important for a11y changes and test it. 12604 */ 12605 @VisibleForTesting setChildImportantForAccessibilityInternal(ViewHolder viewHolder, int importantForAccessibility)12606 boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, 12607 int importantForAccessibility) { 12608 if (isComputingLayout()) { 12609 viewHolder.mPendingAccessibilityState = importantForAccessibility; 12610 mPendingAccessibilityImportanceChange.add(viewHolder); 12611 return false; 12612 } 12613 viewHolder.itemView.setImportantForAccessibility(importantForAccessibility); 12614 return true; 12615 } 12616 dispatchPendingImportantForAccessibilityChanges()12617 void dispatchPendingImportantForAccessibilityChanges() { 12618 for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) { 12619 ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); 12620 if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { 12621 continue; 12622 } 12623 int state = viewHolder.mPendingAccessibilityState; 12624 if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { 12625 //noinspection WrongConstant 12626 viewHolder.itemView.setImportantForAccessibility(state); 12627 viewHolder.mPendingAccessibilityState = 12628 ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; 12629 } 12630 } 12631 mPendingAccessibilityImportanceChange.clear(); 12632 } 12633 getAdapterPositionInRecyclerView(ViewHolder viewHolder)12634 int getAdapterPositionInRecyclerView(ViewHolder viewHolder) { 12635 if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 12636 | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) 12637 || !viewHolder.isBound()) { 12638 return RecyclerView.NO_POSITION; 12639 } 12640 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); 12641 } 12642 12643 @VisibleForTesting initFastScroller(StateListDrawable verticalThumbDrawable, Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, Drawable horizontalTrackDrawable)12644 void initFastScroller(StateListDrawable verticalThumbDrawable, 12645 Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, 12646 Drawable horizontalTrackDrawable) { 12647 if (verticalThumbDrawable == null || verticalTrackDrawable == null 12648 || horizontalThumbDrawable == null || horizontalTrackDrawable == null) { 12649 throw new IllegalArgumentException( 12650 "Trying to set fast scroller without both required drawables." 12651 + exceptionLabel()); 12652 } 12653 12654 Resources resources = getContext().getResources(); 12655 new FastScroller(this, verticalThumbDrawable, verticalTrackDrawable, 12656 horizontalThumbDrawable, horizontalTrackDrawable, 12657 resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness), 12658 resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range), 12659 resources.getDimensionPixelOffset(R.dimen.fastscroll_margin)); 12660 } 12661 12662 // NestedScrollingChild 12663 12664 @Override setNestedScrollingEnabled(boolean enabled)12665 public void setNestedScrollingEnabled(boolean enabled) { 12666 getScrollingChildHelper().setNestedScrollingEnabled(enabled); 12667 } 12668 12669 @Override isNestedScrollingEnabled()12670 public boolean isNestedScrollingEnabled() { 12671 return getScrollingChildHelper().isNestedScrollingEnabled(); 12672 } 12673 12674 @Override startNestedScroll(int axes)12675 public boolean startNestedScroll(int axes) { 12676 return getScrollingChildHelper().startNestedScroll(axes); 12677 } 12678 12679 @Override startNestedScroll(int axes, int type)12680 public boolean startNestedScroll(int axes, int type) { 12681 return getScrollingChildHelper().startNestedScroll(axes, type); 12682 } 12683 12684 @Override stopNestedScroll()12685 public void stopNestedScroll() { 12686 getScrollingChildHelper().stopNestedScroll(); 12687 } 12688 12689 @Override stopNestedScroll(int type)12690 public void stopNestedScroll(int type) { 12691 getScrollingChildHelper().stopNestedScroll(type); 12692 } 12693 12694 @Override hasNestedScrollingParent()12695 public boolean hasNestedScrollingParent() { 12696 return getScrollingChildHelper().hasNestedScrollingParent(); 12697 } 12698 12699 @Override hasNestedScrollingParent(int type)12700 public boolean hasNestedScrollingParent(int type) { 12701 return getScrollingChildHelper().hasNestedScrollingParent(type); 12702 } 12703 12704 @Override dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)12705 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 12706 int dyUnconsumed, int[] offsetInWindow) { 12707 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 12708 dxUnconsumed, dyUnconsumed, offsetInWindow); 12709 } 12710 12711 @Override dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type)12712 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 12713 int dyUnconsumed, int[] offsetInWindow, int type) { 12714 return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 12715 dxUnconsumed, dyUnconsumed, offsetInWindow, type); 12716 } 12717 12718 @Override dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type, int @NonNull [] consumed)12719 public final void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, 12720 int dyUnconsumed, int[] offsetInWindow, int type, int @NonNull [] consumed) { 12721 getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, 12722 dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed); 12723 } 12724 12725 @Override dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow)12726 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 12727 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 12728 } 12729 12730 @Override dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type)12731 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, 12732 int type) { 12733 return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, 12734 type); 12735 } 12736 12737 @Override dispatchNestedFling(float velocityX, float velocityY, boolean consumed)12738 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 12739 return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); 12740 } 12741 12742 @Override dispatchNestedPreFling(float velocityX, float velocityY)12743 public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 12744 return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); 12745 } 12746 12747 /** 12748 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 12749 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 12750 * to create their own subclass of this <code>LayoutParams</code> class 12751 * to store any additional required per-child view metadata about the layout. 12752 */ 12753 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { 12754 ViewHolder mViewHolder; 12755 final Rect mDecorInsets = new Rect(); 12756 boolean mInsetsDirty = true; 12757 // Flag is set to true if the view is bound while it is detached from RV. 12758 // In this case, we need to manually call invalidate after view is added to guarantee that 12759 // invalidation is populated through the View hierarchy 12760 boolean mPendingInvalidate = false; 12761 LayoutParams(Context c, AttributeSet attrs)12762 public LayoutParams(Context c, AttributeSet attrs) { 12763 super(c, attrs); 12764 } 12765 LayoutParams(int width, int height)12766 public LayoutParams(int width, int height) { 12767 super(width, height); 12768 } 12769 LayoutParams(MarginLayoutParams source)12770 public LayoutParams(MarginLayoutParams source) { 12771 super(source); 12772 } 12773 LayoutParams(ViewGroup.LayoutParams source)12774 public LayoutParams(ViewGroup.LayoutParams source) { 12775 super(source); 12776 } 12777 LayoutParams(LayoutParams source)12778 public LayoutParams(LayoutParams source) { 12779 super((ViewGroup.LayoutParams) source); 12780 } 12781 12782 /** 12783 * Returns true if the view this LayoutParams is attached to needs to have its content 12784 * updated from the corresponding adapter. 12785 * 12786 * @return true if the view should have its content updated 12787 */ viewNeedsUpdate()12788 public boolean viewNeedsUpdate() { 12789 return mViewHolder.needsUpdate(); 12790 } 12791 12792 /** 12793 * Returns true if the view this LayoutParams is attached to is now representing 12794 * potentially invalid data. A LayoutManager should scrap/recycle it. 12795 * 12796 * @return true if the view is invalid 12797 */ isViewInvalid()12798 public boolean isViewInvalid() { 12799 return mViewHolder.isInvalid(); 12800 } 12801 12802 /** 12803 * Returns true if the adapter data item corresponding to the view this LayoutParams 12804 * is attached to has been removed from the data set. A LayoutManager may choose to 12805 * treat it differently in order to animate its outgoing or disappearing state. 12806 * 12807 * @return true if the item the view corresponds to was removed from the data set 12808 */ isItemRemoved()12809 public boolean isItemRemoved() { 12810 return mViewHolder.isRemoved(); 12811 } 12812 12813 /** 12814 * Returns true if the adapter data item corresponding to the view this LayoutParams 12815 * is attached to has been changed in the data set. A LayoutManager may choose to 12816 * treat it differently in order to animate its changing state. 12817 * 12818 * @return true if the item the view corresponds to was changed in the data set 12819 */ isItemChanged()12820 public boolean isItemChanged() { 12821 return mViewHolder.isUpdated(); 12822 } 12823 12824 /** 12825 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} 12826 */ 12827 @Deprecated getViewPosition()12828 public int getViewPosition() { 12829 return mViewHolder.getPosition(); 12830 } 12831 12832 /** 12833 * Returns the adapter position that the view this LayoutParams is attached to corresponds 12834 * to as of latest layout calculation. 12835 * 12836 * @return the adapter position this view as of latest layout pass 12837 */ getViewLayoutPosition()12838 public int getViewLayoutPosition() { 12839 return mViewHolder.getLayoutPosition(); 12840 } 12841 12842 /** 12843 * @deprecated This method is confusing when nested adapters are used. 12844 * If you are calling from the context of an {@link Adapter}, 12845 * use {@link #getBindingAdapterPosition()}. If you need the position that 12846 * {@link RecyclerView} sees, use {@link #getAbsoluteAdapterPosition()}. 12847 */ 12848 @Deprecated getViewAdapterPosition()12849 public int getViewAdapterPosition() { 12850 return mViewHolder.getBindingAdapterPosition(); 12851 } 12852 12853 /** 12854 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 12855 * corresponds to in the {@link RecyclerView}. If the {@link RecyclerView} has an 12856 * {@link Adapter} that merges other adapters, this position will be with respect to the 12857 * adapter that is assigned to the {@link RecyclerView}. 12858 * 12859 * @return the up-to-date adapter position this view with respect to the RecyclerView. It 12860 * may return {@link RecyclerView#NO_POSITION} if item represented by this View has been 12861 * removed or 12862 * its up-to-date position cannot be calculated. 12863 */ getAbsoluteAdapterPosition()12864 public int getAbsoluteAdapterPosition() { 12865 return mViewHolder.getAbsoluteAdapterPosition(); 12866 } 12867 12868 /** 12869 * Returns the up-to-date adapter position that the view this LayoutParams is attached to 12870 * corresponds to with respect to the {@link Adapter} that bound this View. 12871 * 12872 * @return the up-to-date adapter position this view relative to the {@link Adapter} that 12873 * bound this View. It may return {@link RecyclerView#NO_POSITION} if item represented by 12874 * this View has been removed or its up-to-date position cannot be calculated. 12875 */ getBindingAdapterPosition()12876 public int getBindingAdapterPosition() { 12877 return mViewHolder.getBindingAdapterPosition(); 12878 } 12879 } 12880 12881 /** 12882 * Observer base class for watching changes to an {@link Adapter}. 12883 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 12884 */ 12885 public abstract static class AdapterDataObserver { onChanged()12886 public void onChanged() { 12887 // Do nothing 12888 } 12889 onItemRangeChanged(int positionStart, int itemCount)12890 public void onItemRangeChanged(int positionStart, int itemCount) { 12891 // do nothing 12892 } 12893 onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload)12894 public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { 12895 // fallback to onItemRangeChanged(positionStart, itemCount) if app 12896 // does not override this method. 12897 onItemRangeChanged(positionStart, itemCount); 12898 } 12899 onItemRangeInserted(int positionStart, int itemCount)12900 public void onItemRangeInserted(int positionStart, int itemCount) { 12901 // do nothing 12902 } 12903 onItemRangeRemoved(int positionStart, int itemCount)12904 public void onItemRangeRemoved(int positionStart, int itemCount) { 12905 // do nothing 12906 } 12907 onItemRangeMoved(int fromPosition, int toPosition, int itemCount)12908 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 12909 // do nothing 12910 } 12911 12912 /** 12913 * Called when the {@link Adapter.StateRestorationPolicy} of the {@link Adapter} changed. 12914 * When this method is called, the Adapter might be ready to restore its state if it has 12915 * not already been restored. 12916 * 12917 * @see Adapter#getStateRestorationPolicy() 12918 * @see Adapter#setStateRestorationPolicy(Adapter.StateRestorationPolicy) 12919 */ onStateRestorationPolicyChanged()12920 public void onStateRestorationPolicyChanged() { 12921 // do nothing 12922 } 12923 } 12924 12925 /** 12926 * Base class for smooth scrolling. Handles basic tracking of the target view position and 12927 * provides methods to trigger a programmatic scroll. 12928 * 12929 * <p>An instance of SmoothScroller is only intended to be used once. You should create a new 12930 * instance for each call to {@link LayoutManager#startSmoothScroll(SmoothScroller)}. 12931 * 12932 * @see LinearSmoothScroller 12933 */ 12934 public abstract static class SmoothScroller { 12935 12936 private int mTargetPosition = RecyclerView.NO_POSITION; 12937 12938 private RecyclerView mRecyclerView; 12939 12940 private LayoutManager mLayoutManager; 12941 12942 private boolean mPendingInitialRun; 12943 12944 private boolean mRunning; 12945 12946 private View mTargetView; 12947 12948 private final Action mRecyclingAction; 12949 12950 private boolean mStarted; 12951 SmoothScroller()12952 public SmoothScroller() { 12953 mRecyclingAction = new Action(0, 0); 12954 } 12955 12956 /** 12957 * Starts a smooth scroll for the given target position. 12958 * <p>In each animation step, {@link RecyclerView} will check 12959 * for the target view and call either 12960 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 12961 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until 12962 * SmoothScroller is stopped.</p> 12963 * 12964 * <p>Note that if RecyclerView finds the target view, it will automatically stop the 12965 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will 12966 * stop calling SmoothScroller in each animation step.</p> 12967 */ start(RecyclerView recyclerView, LayoutManager layoutManager)12968 void start(RecyclerView recyclerView, LayoutManager layoutManager) { 12969 12970 // Stop any previous ViewFlinger animations now because we are about to start a new one. 12971 recyclerView.mViewFlinger.stop(); 12972 12973 if (mStarted) { 12974 Log.w(TAG, "An instance of " + this.getClass().getSimpleName() + " was started " 12975 + "more than once. Each instance of" + this.getClass().getSimpleName() + " " 12976 + "is intended to only be used once. You should create a new instance for " 12977 + "each use."); 12978 } 12979 12980 mRecyclerView = recyclerView; 12981 mLayoutManager = layoutManager; 12982 if (mTargetPosition == RecyclerView.NO_POSITION) { 12983 throw new IllegalArgumentException("Invalid target position"); 12984 } 12985 mRecyclerView.mState.mTargetPosition = mTargetPosition; 12986 mRunning = true; 12987 mPendingInitialRun = true; 12988 mTargetView = findViewByPosition(getTargetPosition()); 12989 onStart(); 12990 mRecyclerView.mViewFlinger.postOnAnimation(); 12991 12992 mStarted = true; 12993 } 12994 setTargetPosition(int targetPosition)12995 public void setTargetPosition(int targetPosition) { 12996 mTargetPosition = targetPosition; 12997 } 12998 12999 /** 13000 * Compute the scroll vector for a given target position. 13001 * <p> 13002 * This method can return null if the layout manager cannot calculate a scroll vector 13003 * for the given position (e.g. it has no current scroll position). 13004 * 13005 * @param targetPosition the position to which the scroller is scrolling 13006 * @return the scroll vector for a given target position 13007 */ computeScrollVectorForPosition(int targetPosition)13008 public @Nullable PointF computeScrollVectorForPosition(int targetPosition) { 13009 LayoutManager layoutManager = getLayoutManager(); 13010 if (layoutManager instanceof ScrollVectorProvider) { 13011 return ((ScrollVectorProvider) layoutManager) 13012 .computeScrollVectorForPosition(targetPosition); 13013 } 13014 Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager" 13015 + " does not implement " + ScrollVectorProvider.class.getCanonicalName()); 13016 return null; 13017 } 13018 13019 /** 13020 * @return The LayoutManager to which this SmoothScroller is attached. Will return 13021 * <code>null</code> after the SmoothScroller is stopped. 13022 */ getLayoutManager()13023 public @Nullable LayoutManager getLayoutManager() { 13024 return mLayoutManager; 13025 } 13026 13027 /** 13028 * Stops running the SmoothScroller in each animation callback. Note that this does not 13029 * cancel any existing {@link Action} updated by 13030 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or 13031 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. 13032 */ stop()13033 protected final void stop() { 13034 if (!mRunning) { 13035 return; 13036 } 13037 mRunning = false; 13038 onStop(); 13039 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; 13040 mTargetView = null; 13041 mTargetPosition = RecyclerView.NO_POSITION; 13042 mPendingInitialRun = false; 13043 // trigger a cleanup 13044 mLayoutManager.onSmoothScrollerStopped(this); 13045 // clear references to avoid any potential leak by a custom smooth scroller 13046 mLayoutManager = null; 13047 mRecyclerView = null; 13048 } 13049 13050 /** 13051 * Returns true if SmoothScroller has been started but has not received the first 13052 * animation 13053 * callback yet. 13054 * 13055 * @return True if this SmoothScroller is waiting to start 13056 */ isPendingInitialRun()13057 public boolean isPendingInitialRun() { 13058 return mPendingInitialRun; 13059 } 13060 13061 13062 /** 13063 * @return True if SmoothScroller is currently active 13064 */ isRunning()13065 public boolean isRunning() { 13066 return mRunning; 13067 } 13068 13069 /** 13070 * Returns the adapter position of the target item 13071 * 13072 * @return Adapter position of the target item or 13073 * {@link RecyclerView#NO_POSITION} if no target view is set. 13074 */ getTargetPosition()13075 public int getTargetPosition() { 13076 return mTargetPosition; 13077 } 13078 onAnimation(int dx, int dy)13079 void onAnimation(int dx, int dy) { 13080 final RecyclerView recyclerView = mRecyclerView; 13081 if (mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) { 13082 stop(); 13083 } 13084 13085 // The following if block exists to have the LayoutManager scroll 1 pixel in the correct 13086 // direction in order to cause the LayoutManager to draw two pages worth of views so 13087 // that the target view may be found before scrolling any further. This is done to 13088 // prevent an initial scroll distance from scrolling past the view, which causes a 13089 // jittery looking animation. 13090 if (mPendingInitialRun && mTargetView == null && mLayoutManager != null) { 13091 PointF pointF = computeScrollVectorForPosition(mTargetPosition); 13092 if (pointF != null && (pointF.x != 0 || pointF.y != 0)) { 13093 recyclerView.scrollStep( 13094 (int) Math.signum(pointF.x), 13095 (int) Math.signum(pointF.y), 13096 null); 13097 } 13098 } 13099 13100 mPendingInitialRun = false; 13101 13102 if (mTargetView != null) { 13103 // verify target position 13104 if (getChildPosition(mTargetView) == mTargetPosition) { 13105 onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction); 13106 mRecyclingAction.runIfNecessary(recyclerView); 13107 stop(); 13108 } else { 13109 Log.e(TAG, "Passed over target position while smooth scrolling."); 13110 mTargetView = null; 13111 } 13112 } 13113 if (mRunning) { 13114 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction); 13115 boolean hadJumpTarget = mRecyclingAction.hasJumpTarget(); 13116 mRecyclingAction.runIfNecessary(recyclerView); 13117 if (hadJumpTarget) { 13118 // It is not stopped so needs to be restarted 13119 if (mRunning) { 13120 mPendingInitialRun = true; 13121 recyclerView.mViewFlinger.postOnAnimation(); 13122 } 13123 } 13124 } 13125 } 13126 13127 /** 13128 * @see RecyclerView#getChildLayoutPosition(android.view.View) 13129 */ getChildPosition(View view)13130 public int getChildPosition(View view) { 13131 return mRecyclerView.getChildLayoutPosition(view); 13132 } 13133 13134 /** 13135 * @see RecyclerView.LayoutManager#getChildCount() 13136 */ getChildCount()13137 public int getChildCount() { 13138 return mRecyclerView.mLayout.getChildCount(); 13139 } 13140 13141 /** 13142 * @see RecyclerView.LayoutManager#findViewByPosition(int) 13143 */ findViewByPosition(int position)13144 public View findViewByPosition(int position) { 13145 return mRecyclerView.mLayout.findViewByPosition(position); 13146 } 13147 13148 /** 13149 * @see RecyclerView#scrollToPosition(int) 13150 * @deprecated Use {@link Action#jumpTo(int)}. 13151 */ 13152 @Deprecated instantScrollToPosition(int position)13153 public void instantScrollToPosition(int position) { 13154 mRecyclerView.scrollToPosition(position); 13155 } 13156 onChildAttachedToWindow(View child)13157 protected void onChildAttachedToWindow(View child) { 13158 if (getChildPosition(child) == getTargetPosition()) { 13159 mTargetView = child; 13160 if (sVerboseLoggingEnabled) { 13161 Log.d(TAG, "smooth scroll target view has been attached"); 13162 } 13163 } 13164 } 13165 13166 /** 13167 * Normalizes the vector. 13168 * 13169 * @param scrollVector The vector that points to the target scroll position 13170 */ normalize(@onNull PointF scrollVector)13171 protected void normalize(@NonNull PointF scrollVector) { 13172 final float magnitude = (float) Math.sqrt(scrollVector.x * scrollVector.x 13173 + scrollVector.y * scrollVector.y); 13174 scrollVector.x /= magnitude; 13175 scrollVector.y /= magnitude; 13176 } 13177 13178 /** 13179 * Called when smooth scroll is started. This might be a good time to do setup. 13180 */ onStart()13181 protected abstract void onStart(); 13182 13183 /** 13184 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. 13185 * 13186 * @see #stop() 13187 */ onStop()13188 protected abstract void onStop(); 13189 13190 /** 13191 * <p>RecyclerView will call this method each time it scrolls until it can find the target 13192 * position in the layout.</p> 13193 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the 13194 * provided {@link Action} to define the next scroll.</p> 13195 * 13196 * @param dx Last scroll amount horizontally 13197 * @param dy Last scroll amount vertically 13198 * @param state Transient state of RecyclerView 13199 * @param action If you want to trigger a new smooth scroll and cancel the previous one, 13200 * update this object. 13201 */ onSeekTargetStep(@x int dx, @Px int dy, @NonNull State state, @NonNull Action action)13202 protected abstract void onSeekTargetStep(@Px int dx, @Px int dy, @NonNull State state, 13203 @NonNull Action action); 13204 13205 /** 13206 * Called when the target position is laid out. This is the last callback SmoothScroller 13207 * will receive and it should update the provided {@link Action} to define the scroll 13208 * details towards the target view. 13209 * 13210 * @param targetView The view element which render the target position. 13211 * @param state Transient state of RecyclerView 13212 * @param action Action instance that you should update to define final scroll action 13213 * towards the targetView 13214 */ onTargetFound(@onNull View targetView, @NonNull State state, @NonNull Action action)13215 protected abstract void onTargetFound(@NonNull View targetView, @NonNull State state, 13216 @NonNull Action action); 13217 13218 /** 13219 * Holds information about a smooth scroll request by a {@link SmoothScroller}. 13220 */ 13221 public static class Action { 13222 13223 public static final int UNDEFINED_DURATION = RecyclerView.UNDEFINED_DURATION; 13224 13225 private int mDx; 13226 13227 private int mDy; 13228 13229 private int mDuration; 13230 13231 private int mJumpToPosition = NO_POSITION; 13232 13233 private Interpolator mInterpolator; 13234 13235 private boolean mChanged = false; 13236 13237 // we track this variable to inform custom implementer if they are updating the action 13238 // in every animation callback 13239 private int mConsecutiveUpdates = 0; 13240 13241 /** 13242 * @param dx Pixels to scroll horizontally 13243 * @param dy Pixels to scroll vertically 13244 */ Action(@x int dx, @Px int dy)13245 public Action(@Px int dx, @Px int dy) { 13246 this(dx, dy, UNDEFINED_DURATION, null); 13247 } 13248 13249 /** 13250 * @param dx Pixels to scroll horizontally 13251 * @param dy Pixels to scroll vertically 13252 * @param duration Duration of the animation in milliseconds 13253 */ Action(@x int dx, @Px int dy, int duration)13254 public Action(@Px int dx, @Px int dy, int duration) { 13255 this(dx, dy, duration, null); 13256 } 13257 13258 /** 13259 * @param dx Pixels to scroll horizontally 13260 * @param dy Pixels to scroll vertically 13261 * @param duration Duration of the animation in milliseconds 13262 * @param interpolator Interpolator to be used when calculating scroll position in each 13263 * animation step 13264 */ Action(@x int dx, @Px int dy, int duration, @Nullable Interpolator interpolator)13265 public Action(@Px int dx, @Px int dy, int duration, 13266 @Nullable Interpolator interpolator) { 13267 mDx = dx; 13268 mDy = dy; 13269 mDuration = duration; 13270 mInterpolator = interpolator; 13271 } 13272 13273 /** 13274 * Instead of specifying pixels to scroll, use the target position to jump using 13275 * {@link RecyclerView#scrollToPosition(int)}. 13276 * <p> 13277 * You may prefer using this method if scroll target is really far away and you prefer 13278 * to jump to a location and smooth scroll afterwards. 13279 * <p> 13280 * Note that calling this method takes priority over other update methods such as 13281 * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)}, 13282 * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call 13283 * {@link #jumpTo(int)}, the other changes will not be considered for this animation 13284 * frame. 13285 * 13286 * @param targetPosition The target item position to scroll to using instant scrolling. 13287 */ jumpTo(int targetPosition)13288 public void jumpTo(int targetPosition) { 13289 mJumpToPosition = targetPosition; 13290 } 13291 hasJumpTarget()13292 boolean hasJumpTarget() { 13293 return mJumpToPosition >= 0; 13294 } 13295 runIfNecessary(RecyclerView recyclerView)13296 void runIfNecessary(RecyclerView recyclerView) { 13297 if (mJumpToPosition >= 0) { 13298 final int position = mJumpToPosition; 13299 mJumpToPosition = NO_POSITION; 13300 recyclerView.jumpToPositionForSmoothScroller(position); 13301 mChanged = false; 13302 return; 13303 } 13304 if (mChanged) { 13305 validate(); 13306 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator); 13307 mConsecutiveUpdates++; 13308 if (mConsecutiveUpdates > 10) { 13309 // A new action is being set in every animation step. This looks like a bad 13310 // implementation. Inform developer. 13311 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" 13312 + " you are not changing it unless necessary"); 13313 } 13314 mChanged = false; 13315 } else { 13316 mConsecutiveUpdates = 0; 13317 } 13318 } 13319 validate()13320 private void validate() { 13321 if (mInterpolator != null && mDuration < 1) { 13322 throw new IllegalStateException("If you provide an interpolator, you must" 13323 + " set a positive duration"); 13324 } else if (mDuration < 1) { 13325 throw new IllegalStateException("Scroll duration must be a positive number"); 13326 } 13327 } 13328 13329 @Px getDx()13330 public int getDx() { 13331 return mDx; 13332 } 13333 setDx(@x int dx)13334 public void setDx(@Px int dx) { 13335 mChanged = true; 13336 mDx = dx; 13337 } 13338 13339 @Px getDy()13340 public int getDy() { 13341 return mDy; 13342 } 13343 setDy(@x int dy)13344 public void setDy(@Px int dy) { 13345 mChanged = true; 13346 mDy = dy; 13347 } 13348 getDuration()13349 public int getDuration() { 13350 return mDuration; 13351 } 13352 setDuration(int duration)13353 public void setDuration(int duration) { 13354 mChanged = true; 13355 mDuration = duration; 13356 } 13357 getInterpolator()13358 public @Nullable Interpolator getInterpolator() { 13359 return mInterpolator; 13360 } 13361 13362 /** 13363 * Sets the interpolator to calculate scroll steps 13364 * 13365 * @param interpolator The interpolator to use. If you specify an interpolator, you must 13366 * also set the duration. 13367 * @see #setDuration(int) 13368 */ setInterpolator(@ullable Interpolator interpolator)13369 public void setInterpolator(@Nullable Interpolator interpolator) { 13370 mChanged = true; 13371 mInterpolator = interpolator; 13372 } 13373 13374 /** 13375 * Updates the action with given parameters. 13376 * 13377 * @param dx Pixels to scroll horizontally 13378 * @param dy Pixels to scroll vertically 13379 * @param duration Duration of the animation in milliseconds 13380 * @param interpolator Interpolator to be used when calculating scroll position in each 13381 * animation step 13382 */ update(@x int dx, @Px int dy, int duration, @Nullable Interpolator interpolator)13383 public void update(@Px int dx, @Px int dy, int duration, 13384 @Nullable Interpolator interpolator) { 13385 mDx = dx; 13386 mDy = dy; 13387 mDuration = duration; 13388 mInterpolator = interpolator; 13389 mChanged = true; 13390 } 13391 } 13392 13393 /** 13394 * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} 13395 * to provide a hint to a {@link SmoothScroller} about the location of the target position. 13396 */ 13397 public interface ScrollVectorProvider { 13398 /** 13399 * Should calculate the vector that points to the direction where the target position 13400 * can be found. 13401 * <p> 13402 * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards 13403 * the target position. 13404 * <p> 13405 * The magnitude of the vector is not important. It is always normalized before being 13406 * used by the {@link LinearSmoothScroller}. 13407 * <p> 13408 * LayoutManager should not check whether the position exists in the adapter or not. 13409 * 13410 * @param targetPosition the target position to which the returned vector should point 13411 * @return the scroll vector for a given position. 13412 */ computeScrollVectorForPosition(int targetPosition)13413 @Nullable PointF computeScrollVectorForPosition(int targetPosition); 13414 } 13415 } 13416 13417 static class AdapterDataObservable extends Observable<AdapterDataObserver> { hasObservers()13418 public boolean hasObservers() { 13419 return !mObservers.isEmpty(); 13420 } 13421 notifyChanged()13422 public void notifyChanged() { 13423 // since onChanged() is implemented by the app, it could do anything, including 13424 // removing itself from {@link mObservers} - and that could cause problems if 13425 // an iterator is used on the ArrayList {@link mObservers}. 13426 // to avoid such problems, just march thru the list in the reverse order. 13427 for (int i = mObservers.size() - 1; i >= 0; i--) { 13428 mObservers.get(i).onChanged(); 13429 } 13430 } 13431 notifyStateRestorationPolicyChanged()13432 public void notifyStateRestorationPolicyChanged() { 13433 for (int i = mObservers.size() - 1; i >= 0; i--) { 13434 mObservers.get(i).onStateRestorationPolicyChanged(); 13435 } 13436 } 13437 notifyItemRangeChanged(int positionStart, int itemCount)13438 public void notifyItemRangeChanged(int positionStart, int itemCount) { 13439 notifyItemRangeChanged(positionStart, itemCount, null); 13440 } 13441 notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload)13442 public void notifyItemRangeChanged(int positionStart, int itemCount, 13443 @Nullable Object payload) { 13444 // since onItemRangeChanged() is implemented by the app, it could do anything, including 13445 // removing itself from {@link mObservers} - and that could cause problems if 13446 // an iterator is used on the ArrayList {@link mObservers}. 13447 // to avoid such problems, just march thru the list in the reverse order. 13448 for (int i = mObservers.size() - 1; i >= 0; i--) { 13449 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); 13450 } 13451 } 13452 notifyItemRangeInserted(int positionStart, int itemCount)13453 public void notifyItemRangeInserted(int positionStart, int itemCount) { 13454 // since onItemRangeInserted() is implemented by the app, it could do anything, 13455 // including removing itself from {@link mObservers} - and that could cause problems if 13456 // an iterator is used on the ArrayList {@link mObservers}. 13457 // to avoid such problems, just march thru the list in the reverse order. 13458 for (int i = mObservers.size() - 1; i >= 0; i--) { 13459 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 13460 } 13461 } 13462 notifyItemRangeRemoved(int positionStart, int itemCount)13463 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 13464 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 13465 // removing itself from {@link mObservers} - and that could cause problems if 13466 // an iterator is used on the ArrayList {@link mObservers}. 13467 // to avoid such problems, just march thru the list in the reverse order. 13468 for (int i = mObservers.size() - 1; i >= 0; i--) { 13469 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 13470 } 13471 } 13472 notifyItemMoved(int fromPosition, int toPosition)13473 public void notifyItemMoved(int fromPosition, int toPosition) { 13474 for (int i = mObservers.size() - 1; i >= 0; i--) { 13475 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); 13476 } 13477 } 13478 } 13479 13480 /** 13481 * This is public so that the CREATOR can be accessed on cold launch. 13482 * 13483 */ 13484 @RestrictTo(LIBRARY) 13485 public static class SavedState extends AbsSavedState { 13486 13487 Parcelable mLayoutState; 13488 13489 /** 13490 * called by CREATOR 13491 */ 13492 @SuppressWarnings("deprecation") SavedState(Parcel in, ClassLoader loader)13493 SavedState(Parcel in, ClassLoader loader) { 13494 super(in, loader); 13495 mLayoutState = in.readParcelable( 13496 loader != null ? loader : LayoutManager.class.getClassLoader()); 13497 } 13498 13499 /** 13500 * Called by onSaveInstanceState 13501 */ SavedState(Parcelable superState)13502 SavedState(Parcelable superState) { 13503 super(superState); 13504 } 13505 13506 @Override writeToParcel(Parcel dest, int flags)13507 public void writeToParcel(Parcel dest, int flags) { 13508 super.writeToParcel(dest, flags); 13509 dest.writeParcelable(mLayoutState, 0); 13510 } 13511 copyFrom(SavedState other)13512 void copyFrom(SavedState other) { 13513 mLayoutState = other.mLayoutState; 13514 } 13515 13516 public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { 13517 @Override 13518 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 13519 return new SavedState(in, loader); 13520 } 13521 13522 @Override 13523 public SavedState createFromParcel(Parcel in) { 13524 return new SavedState(in, null); 13525 } 13526 13527 @Override 13528 public SavedState[] newArray(int size) { 13529 return new SavedState[size]; 13530 } 13531 }; 13532 } 13533 13534 /** 13535 * <p>Contains useful information about the current RecyclerView state like target scroll 13536 * position or view focus. State object can also keep arbitrary data, identified by resource 13537 * ids.</p> 13538 * <p>Often times, RecyclerView components will need to pass information between each other. 13539 * To provide a well defined data bus between components, RecyclerView passes the same State 13540 * object to component callbacks and these components can use it to exchange data.</p> 13541 * <p>If you implement custom components, you can use State's put/get/remove methods to pass 13542 * data between your components without needing to manage their lifecycles.</p> 13543 */ 13544 public static class State { 13545 static final int STEP_START = 1; 13546 static final int STEP_LAYOUT = 1 << 1; 13547 static final int STEP_ANIMATIONS = 1 << 2; 13548 assertLayoutStep(int accepted)13549 void assertLayoutStep(int accepted) { 13550 if ((accepted & mLayoutStep) == 0) { 13551 throw new IllegalStateException("Layout state should be one of " 13552 + Integer.toBinaryString(accepted) + " but it is " 13553 + Integer.toBinaryString(mLayoutStep)); 13554 } 13555 } 13556 13557 13558 /** Owned by SmoothScroller */ 13559 int mTargetPosition = RecyclerView.NO_POSITION; 13560 13561 private SparseArray<Object> mData; 13562 13563 //////////////////////////////////////////////////////////////////////////////////////////// 13564 // Fields below are carried from one layout pass to the next 13565 //////////////////////////////////////////////////////////////////////////////////////////// 13566 13567 /** 13568 * Number of items adapter had in the previous layout. 13569 */ 13570 int mPreviousLayoutItemCount = 0; 13571 13572 /** 13573 * Number of items that were NOT laid out but has been deleted from the adapter after the 13574 * previous layout. 13575 */ 13576 int mDeletedInvisibleItemCountSincePreviousLayout = 0; 13577 13578 //////////////////////////////////////////////////////////////////////////////////////////// 13579 // Fields below must be updated or cleared before they are used (generally before a pass) 13580 //////////////////////////////////////////////////////////////////////////////////////////// 13581 13582 @IntDef(flag = true, value = { 13583 STEP_START, STEP_LAYOUT, STEP_ANIMATIONS 13584 }) 13585 @Retention(RetentionPolicy.SOURCE) 13586 @interface LayoutState { 13587 } 13588 13589 @LayoutState 13590 int mLayoutStep = STEP_START; 13591 13592 /** 13593 * Number of items adapter has. 13594 */ 13595 int mItemCount = 0; 13596 13597 boolean mStructureChanged = false; 13598 13599 /** 13600 * True if the associated {@link RecyclerView} is in the pre-layout step where it is having 13601 * its {@link LayoutManager} layout items where they will be at the beginning of a set of 13602 * predictive item animations. 13603 */ 13604 boolean mInPreLayout = false; 13605 13606 boolean mTrackOldChangeHolders = false; 13607 13608 boolean mIsMeasuring = false; 13609 13610 //////////////////////////////////////////////////////////////////////////////////////////// 13611 // Fields below are always reset outside of the pass (or passes) that use them 13612 //////////////////////////////////////////////////////////////////////////////////////////// 13613 13614 boolean mRunSimpleAnimations = false; 13615 13616 boolean mRunPredictiveAnimations = false; 13617 13618 /** 13619 * This data is saved before a layout calculation happens. After the layout is finished, 13620 * if the previously focused view has been replaced with another view for the same item, we 13621 * move the focus to the new item automatically. 13622 */ 13623 int mFocusedItemPosition; 13624 long mFocusedItemId; 13625 // when a sub child has focus, record its id and see if we can directly request focus on 13626 // that one instead 13627 int mFocusedSubChildId; 13628 13629 int mRemainingScrollHorizontal; 13630 int mRemainingScrollVertical; 13631 13632 //////////////////////////////////////////////////////////////////////////////////////////// 13633 13634 /** 13635 * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially 13636 * prior to any layout passes. 13637 * 13638 * <p>Don't touch any state stored between layout passes, only reset per-layout state, so 13639 * that Recycler#getViewForPosition() can function safely.</p> 13640 */ prepareForNestedPrefetch(Adapter adapter)13641 void prepareForNestedPrefetch(Adapter adapter) { 13642 mLayoutStep = STEP_START; 13643 mItemCount = adapter.getItemCount(); 13644 mInPreLayout = false; 13645 mTrackOldChangeHolders = false; 13646 mIsMeasuring = false; 13647 } 13648 13649 /** 13650 * Returns true if the RecyclerView is currently measuring the layout. This value is 13651 * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView 13652 * has non-exact measurement specs. 13653 * <p> 13654 * Note that if the LayoutManager supports predictive animations and it is calculating the 13655 * pre-layout step, this value will be {@code false} even if the RecyclerView is in 13656 * {@code onMeasure} call. This is because pre-layout means the previous state of the 13657 * RecyclerView and measurements made for that state cannot change the RecyclerView's size. 13658 * LayoutManager is always guaranteed to receive another call to 13659 * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. 13660 * 13661 * @return True if the RecyclerView is currently calculating its bounds, false otherwise. 13662 */ isMeasuring()13663 public boolean isMeasuring() { 13664 return mIsMeasuring; 13665 } 13666 13667 /** 13668 * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its 13669 * {@link LayoutManager} layout items where they will be at the beginning of a set of 13670 * predictive item animations. 13671 */ isPreLayout()13672 public boolean isPreLayout() { 13673 return mInPreLayout; 13674 } 13675 13676 /** 13677 * Returns whether RecyclerView will run predictive animations in this layout pass 13678 * or not. 13679 * 13680 * @return true if RecyclerView is calculating predictive animations to be run at the end 13681 * of the layout pass. 13682 */ willRunPredictiveAnimations()13683 public boolean willRunPredictiveAnimations() { 13684 return mRunPredictiveAnimations; 13685 } 13686 13687 /** 13688 * Returns whether RecyclerView will run simple animations in this layout pass 13689 * or not. 13690 * 13691 * @return true if RecyclerView is calculating simple animations to be run at the end of 13692 * the layout pass. 13693 */ willRunSimpleAnimations()13694 public boolean willRunSimpleAnimations() { 13695 return mRunSimpleAnimations; 13696 } 13697 13698 /** 13699 * Removes the mapping from the specified id, if there was any. 13700 * 13701 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to 13702 * preserve cross functionality and avoid conflicts. 13703 */ remove(int resourceId)13704 public void remove(int resourceId) { 13705 if (mData == null) { 13706 return; 13707 } 13708 mData.remove(resourceId); 13709 } 13710 13711 /** 13712 * Gets the Object mapped from the specified id, or <code>null</code> 13713 * if no such data exists. 13714 * 13715 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* 13716 * to 13717 * preserve cross functionality and avoid conflicts. 13718 */ 13719 @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"}) get(int resourceId)13720 public <T> T get(int resourceId) { 13721 if (mData == null) { 13722 return null; 13723 } 13724 return (T) mData.get(resourceId); 13725 } 13726 13727 /** 13728 * Adds a mapping from the specified id to the specified value, replacing the previous 13729 * mapping from the specified key if there was one. 13730 * 13731 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to 13732 * preserve cross functionality and avoid conflicts. 13733 * @param data The data you want to associate with the resourceId. 13734 */ put(int resourceId, Object data)13735 public void put(int resourceId, Object data) { 13736 if (mData == null) { 13737 mData = new SparseArray<Object>(); 13738 } 13739 mData.put(resourceId, data); 13740 } 13741 13742 /** 13743 * If scroll is triggered to make a certain item visible, this value will return the 13744 * adapter index of that item. 13745 * 13746 * @return Adapter index of the target item or 13747 * {@link RecyclerView#NO_POSITION} if there is no target 13748 * position. 13749 */ getTargetScrollPosition()13750 public int getTargetScrollPosition() { 13751 return mTargetPosition; 13752 } 13753 13754 /** 13755 * Returns if current scroll has a target position. 13756 * 13757 * @return true if scroll is being triggered to make a certain position visible 13758 * @see #getTargetScrollPosition() 13759 */ hasTargetScrollPosition()13760 public boolean hasTargetScrollPosition() { 13761 return mTargetPosition != RecyclerView.NO_POSITION; 13762 } 13763 13764 /** 13765 * @return true if the structure of the data set has changed since the last call to 13766 * onLayoutChildren, false otherwise 13767 */ didStructureChange()13768 public boolean didStructureChange() { 13769 return mStructureChanged; 13770 } 13771 13772 /** 13773 * Returns the total number of items that can be laid out. Note that this number is not 13774 * necessarily equal to the number of items in the adapter, so you should always use this 13775 * number for your position calculations and never access the adapter directly. 13776 * <p> 13777 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter 13778 * data changes on existing Views. These calculations are used to decide which animations 13779 * should be run. 13780 * <p> 13781 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to 13782 * present the correct state to LayoutManager in pre-layout pass. 13783 * <p> 13784 * For example, a newly added item is not included in pre-layout item count because 13785 * pre-layout reflects the contents of the adapter before the item is added. Behind the 13786 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that 13787 * LayoutManager does not know about the new item's existence in pre-layout. The item will 13788 * be available in second layout pass and will be included in the item count. Similar 13789 * adjustments are made for moved and removed items as well. 13790 * <p> 13791 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. 13792 * 13793 * @return The number of items currently available 13794 * @see LayoutManager#getItemCount() 13795 */ getItemCount()13796 public int getItemCount() { 13797 return mInPreLayout 13798 ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) 13799 : mItemCount; 13800 } 13801 13802 /** 13803 * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/ 13804 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 13805 * other than {@link #SCROLL_STATE_SETTLING}. 13806 * 13807 * @return Remaining horizontal scroll distance 13808 */ getRemainingScrollHorizontal()13809 public int getRemainingScrollHorizontal() { 13810 return mRemainingScrollHorizontal; 13811 } 13812 13813 /** 13814 * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/ 13815 * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is 13816 * other than {@link #SCROLL_STATE_SETTLING}. 13817 * 13818 * @return Remaining vertical scroll distance 13819 */ getRemainingScrollVertical()13820 public int getRemainingScrollVertical() { 13821 return mRemainingScrollVertical; 13822 } 13823 13824 @Override toString()13825 public String toString() { 13826 return "State{" 13827 + "mTargetPosition=" + mTargetPosition 13828 + ", mData=" + mData 13829 + ", mItemCount=" + mItemCount 13830 + ", mIsMeasuring=" + mIsMeasuring 13831 + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount 13832 + ", mDeletedInvisibleItemCountSincePreviousLayout=" 13833 + mDeletedInvisibleItemCountSincePreviousLayout 13834 + ", mStructureChanged=" + mStructureChanged 13835 + ", mInPreLayout=" + mInPreLayout 13836 + ", mRunSimpleAnimations=" + mRunSimpleAnimations 13837 + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations 13838 + '}'; 13839 } 13840 } 13841 13842 /** 13843 * This class defines the behavior of fling if the developer wishes to handle it. 13844 * <p> 13845 * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. 13846 * 13847 * @see #setOnFlingListener(OnFlingListener) 13848 */ 13849 public abstract static class OnFlingListener { 13850 13851 /** 13852 * Override this to handle a fling given the velocities in both x and y directions. 13853 * Note that this method will only be called if the associated {@link LayoutManager} 13854 * supports scrolling and the fling is not handled by nested scrolls first. 13855 * 13856 * @param velocityX the fling velocity on the X axis 13857 * @param velocityY the fling velocity on the Y axis 13858 * @return true if the fling was handled, false otherwise. 13859 */ onFling(int velocityX, int velocityY)13860 public abstract boolean onFling(int velocityX, int velocityY); 13861 } 13862 13863 /** 13864 * Internal listener that manages items after animations finish. This is how items are 13865 * retained (not recycled) during animations, but allowed to be recycled afterwards. 13866 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() 13867 * method on the animator's listener when it is done animating any item. 13868 */ 13869 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { 13870 ItemAnimatorRestoreListener()13871 ItemAnimatorRestoreListener() { 13872 } 13873 13874 @Override onAnimationFinished(ViewHolder item)13875 public void onAnimationFinished(ViewHolder item) { 13876 item.setIsRecyclable(true); 13877 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh 13878 item.mShadowedHolder = null; 13879 } 13880 // always null this because an OldViewHolder can never become NewViewHolder w/o being 13881 // recycled. 13882 item.mShadowingHolder = null; 13883 if (!item.shouldBeKeptAsChild()) { 13884 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { 13885 removeDetachedView(item.itemView, false); 13886 } 13887 } 13888 } 13889 } 13890 13891 /** 13892 * This class defines the animations that take place on items as changes are made 13893 * to the adapter. 13894 * 13895 * Subclasses of ItemAnimator can be used to implement custom animations for actions on 13896 * ViewHolder items. The RecyclerView will manage retaining these items while they 13897 * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} 13898 * when a ViewHolder's animation is finished. In other words, there must be a matching 13899 * {@link #dispatchAnimationFinished(ViewHolder)} call for each 13900 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, 13901 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 13902 * animateChange()} 13903 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, 13904 * and 13905 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 13906 * animateDisappearance()} call. 13907 * 13908 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p> 13909 * 13910 * @see #setItemAnimator(ItemAnimator) 13911 */ 13912 @SuppressWarnings("UnusedParameters") 13913 public abstract static class ItemAnimator { 13914 13915 /** 13916 * The Item represented by this ViewHolder is updated. 13917 * <p> 13918 * 13919 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 13920 */ 13921 public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; 13922 13923 /** 13924 * The Item represented by this ViewHolder is removed from the adapter. 13925 * <p> 13926 * 13927 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 13928 */ 13929 public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; 13930 13931 /** 13932 * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content 13933 * represented by this ViewHolder is invalid. 13934 * <p> 13935 * 13936 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 13937 */ 13938 public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; 13939 13940 /** 13941 * The position of the Item represented by this ViewHolder has been changed. This flag is 13942 * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to 13943 * any adapter change that may have a side effect on this item. (e.g. The item before this 13944 * one has been removed from the Adapter). 13945 * <p> 13946 * 13947 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 13948 */ 13949 public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; 13950 13951 /** 13952 * This ViewHolder was not laid out but has been added to the layout in pre-layout state 13953 * by the {@link LayoutManager}. This means that the item was already in the Adapter but 13954 * invisible and it may become visible in the post layout phase. LayoutManagers may prefer 13955 * to add new items in pre-layout to specify their virtual location when they are invisible 13956 * (e.g. to specify the item should <i>animate in</i> from below the visible area). 13957 * <p> 13958 * 13959 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 13960 */ 13961 public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 13962 ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; 13963 13964 /** 13965 * The set of flags that might be passed to 13966 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 13967 */ 13968 @IntDef(flag = true, value = { 13969 FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, 13970 FLAG_APPEARED_IN_PRE_LAYOUT 13971 }) 13972 @Retention(RetentionPolicy.SOURCE) 13973 public @interface AdapterChanges { 13974 } 13975 13976 private ItemAnimatorListener mListener = null; 13977 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = 13978 new ArrayList<ItemAnimatorFinishedListener>(); 13979 13980 private long mAddDuration = 120; 13981 private long mRemoveDuration = 120; 13982 private long mMoveDuration = 250; 13983 private long mChangeDuration = 250; 13984 13985 /** 13986 * Gets the current duration for which all move animations will run. 13987 * 13988 * @return The current move duration 13989 */ getMoveDuration()13990 public long getMoveDuration() { 13991 return mMoveDuration; 13992 } 13993 13994 /** 13995 * Sets the duration for which all move animations will run. 13996 * 13997 * @param moveDuration The move duration 13998 */ setMoveDuration(long moveDuration)13999 public void setMoveDuration(long moveDuration) { 14000 mMoveDuration = moveDuration; 14001 } 14002 14003 /** 14004 * Gets the current duration for which all add animations will run. 14005 * 14006 * @return The current add duration 14007 */ getAddDuration()14008 public long getAddDuration() { 14009 return mAddDuration; 14010 } 14011 14012 /** 14013 * Sets the duration for which all add animations will run. 14014 * 14015 * @param addDuration The add duration 14016 */ setAddDuration(long addDuration)14017 public void setAddDuration(long addDuration) { 14018 mAddDuration = addDuration; 14019 } 14020 14021 /** 14022 * Gets the current duration for which all remove animations will run. 14023 * 14024 * @return The current remove duration 14025 */ getRemoveDuration()14026 public long getRemoveDuration() { 14027 return mRemoveDuration; 14028 } 14029 14030 /** 14031 * Sets the duration for which all remove animations will run. 14032 * 14033 * @param removeDuration The remove duration 14034 */ setRemoveDuration(long removeDuration)14035 public void setRemoveDuration(long removeDuration) { 14036 mRemoveDuration = removeDuration; 14037 } 14038 14039 /** 14040 * Gets the current duration for which all change animations will run. 14041 * 14042 * @return The current change duration 14043 */ getChangeDuration()14044 public long getChangeDuration() { 14045 return mChangeDuration; 14046 } 14047 14048 /** 14049 * Sets the duration for which all change animations will run. 14050 * 14051 * @param changeDuration The change duration 14052 */ setChangeDuration(long changeDuration)14053 public void setChangeDuration(long changeDuration) { 14054 mChangeDuration = changeDuration; 14055 } 14056 14057 /** 14058 * Internal only: 14059 * Sets the listener that must be called when the animator is finished 14060 * animating the item (or immediately if no animation happens). This is set 14061 * internally and is not intended to be set by external code. 14062 * 14063 * @param listener The listener that must be called. 14064 */ setListener(ItemAnimatorListener listener)14065 void setListener(ItemAnimatorListener listener) { 14066 mListener = listener; 14067 } 14068 14069 /** 14070 * Called by the RecyclerView before the layout begins. Item animator should record 14071 * necessary information about the View before it is potentially rebound, moved or removed. 14072 * <p> 14073 * The data returned from this method will be passed to the related <code>animate**</code> 14074 * methods. 14075 * <p> 14076 * Note that this method may be called after pre-layout phase if LayoutManager adds new 14077 * Views to the layout in pre-layout pass. 14078 * <p> 14079 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 14080 * the View and the adapter change flags. 14081 * 14082 * @param state The current State of RecyclerView which includes some useful data 14083 * about the layout that will be calculated. 14084 * @param viewHolder The ViewHolder whose information should be recorded. 14085 * @param changeFlags Additional information about what changes happened in the Adapter 14086 * about the Item represented by this ViewHolder. For instance, if 14087 * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. 14088 * @param payloads The payload list that was previously passed to 14089 * {@link Adapter#notifyItemChanged(int, Object)} or 14090 * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. 14091 * @return An ItemHolderInfo instance that preserves necessary information about the 14092 * ViewHolder. This object will be passed back to related <code>animate**</code> methods 14093 * after layout is complete. 14094 * @see #recordPostLayoutInformation(State, ViewHolder) 14095 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14096 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14097 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14098 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14099 */ recordPreLayoutInformation(@onNull State state, @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, @NonNull List<Object> payloads)14100 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, 14101 @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, 14102 @NonNull List<Object> payloads) { 14103 return obtainHolderInfo().setFrom(viewHolder); 14104 } 14105 14106 /** 14107 * Called by the RecyclerView after the layout is complete. Item animator should record 14108 * necessary information about the View's final state. 14109 * <p> 14110 * The data returned from this method will be passed to the related <code>animate**</code> 14111 * methods. 14112 * <p> 14113 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of 14114 * the View. 14115 * 14116 * @param state The current State of RecyclerView which includes some useful data about 14117 * the layout that will be calculated. 14118 * @param viewHolder The ViewHolder whose information should be recorded. 14119 * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. 14120 * This object will be passed back to related <code>animate**</code> methods when 14121 * RecyclerView decides how items should be animated. 14122 * @see #recordPreLayoutInformation(State, ViewHolder, int, List) 14123 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14124 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14125 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14126 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14127 */ recordPostLayoutInformation(@onNull State state, @NonNull ViewHolder viewHolder)14128 public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, 14129 @NonNull ViewHolder viewHolder) { 14130 return obtainHolderInfo().setFrom(viewHolder); 14131 } 14132 14133 /** 14134 * Called by the RecyclerView when a ViewHolder has disappeared from the layout. 14135 * <p> 14136 * This means that the View was a child of the LayoutManager when layout started but has 14137 * been removed by the LayoutManager. It might have been removed from the adapter or simply 14138 * become invisible due to other factors. You can distinguish these two cases by checking 14139 * the change flags that were passed to 14140 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14141 * <p> 14142 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 14143 * animation callback method which will be called by the RecyclerView depends on the 14144 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 14145 * LayoutManager's decision whether to layout the changed version of a disappearing 14146 * ViewHolder or not. RecyclerView will call 14147 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14148 * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator 14149 * returns {@code false} from 14150 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 14151 * LayoutManager lays out a new disappearing view that holds the updated information. 14152 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 14153 * <p> 14154 * If LayoutManager supports predictive animations, it might provide a target disappear 14155 * location for the View by laying it out in that location. When that happens, 14156 * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the 14157 * response of that call will be passed to this method as the <code>postLayoutInfo</code>. 14158 * <p> 14159 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 14160 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 14161 * decides not to animate the view). 14162 * 14163 * @param viewHolder The ViewHolder which should be animated 14164 * @param preLayoutInfo The information that was returned from 14165 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14166 * @param postLayoutInfo The information that was returned from 14167 * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be 14168 * null if the LayoutManager did not layout the item. 14169 * @return true if a later call to {@link #runPendingAnimations()} is requested, 14170 * false otherwise. 14171 */ animateDisappearance(@onNull ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo)14172 public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, 14173 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); 14174 14175 /** 14176 * Called by the RecyclerView when a ViewHolder is added to the layout. 14177 * <p> 14178 * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started 14179 * but has been added by the LayoutManager. It might be newly added to the adapter or 14180 * simply become visible due to other factors. 14181 * <p> 14182 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 14183 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 14184 * decides not to animate the view). 14185 * 14186 * @param viewHolder The ViewHolder which should be animated 14187 * @param preLayoutInfo The information that was returned from 14188 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14189 * Might be null if Item was just added to the adapter or 14190 * LayoutManager does not support predictive animations or it could 14191 * not predict that this ViewHolder will become visible. 14192 * @param postLayoutInfo The information that was returned from {@link 14193 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14194 * @return true if a later call to {@link #runPendingAnimations()} is requested, 14195 * false otherwise. 14196 */ animateAppearance(@onNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)14197 public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, 14198 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 14199 14200 /** 14201 * Called by the RecyclerView when a ViewHolder is present in both before and after the 14202 * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call 14203 * for it or a {@link Adapter#notifyDataSetChanged()} call. 14204 * <p> 14205 * This ViewHolder still represents the same data that it was representing when the layout 14206 * started but its position / size may be changed by the LayoutManager. 14207 * <p> 14208 * If the Item's layout position didn't change, RecyclerView still calls this method because 14209 * it does not track this information (or does not necessarily know that an animation is 14210 * not required). Your ItemAnimator should handle this case and if there is nothing to 14211 * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return 14212 * <code>false</code>. 14213 * <p> 14214 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation 14215 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it 14216 * decides not to animate the view). 14217 * 14218 * @param viewHolder The ViewHolder which should be animated 14219 * @param preLayoutInfo The information that was returned from 14220 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14221 * @param postLayoutInfo The information that was returned from {@link 14222 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14223 * @return true if a later call to {@link #runPendingAnimations()} is requested, 14224 * false otherwise. 14225 */ animatePersistence(@onNull ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)14226 public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, 14227 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 14228 14229 /** 14230 * Called by the RecyclerView when an adapter item is present both before and after the 14231 * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call 14232 * for it. This method may also be called when 14233 * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that 14234 * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when 14235 * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called, 14236 * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be 14237 * called for the new ViewHolder and the old one will be recycled. 14238 * <p> 14239 * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is 14240 * a good possibility that item contents didn't really change but it is rebound from the 14241 * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the 14242 * screen didn't change and your animator should handle this case as well and avoid creating 14243 * unnecessary animations. 14244 * <p> 14245 * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the 14246 * previous presentation of the item as-is and supply a new ViewHolder for the updated 14247 * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. 14248 * This is useful if you don't know the contents of the Item and would like 14249 * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). 14250 * <p> 14251 * When you are writing a custom item animator for your layout, it might be more performant 14252 * and elegant to re-use the same ViewHolder and animate the content changes manually. 14253 * <p> 14254 * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. 14255 * If the Item's view type has changed or ItemAnimator returned <code>false</code> for 14256 * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the 14257 * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances 14258 * which represent the same Item. In that case, only the new ViewHolder is visible 14259 * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. 14260 * <p> 14261 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct 14262 * ViewHolder when their animation is complete 14263 * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to 14264 * animate the view). 14265 * <p> 14266 * If oldHolder and newHolder are the same instance, you should call 14267 * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>. 14268 * <p> 14269 * Note that when a ViewHolder both changes and disappears in the same layout pass, the 14270 * animation callback method which will be called by the RecyclerView depends on the 14271 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the 14272 * LayoutManager's decision whether to layout the changed version of a disappearing 14273 * ViewHolder or not. RecyclerView will call 14274 * {@code animateChange} instead of 14275 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14276 * animateDisappearance} if and only if the ItemAnimator returns {@code false} from 14277 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the 14278 * LayoutManager lays out a new disappearing view that holds the updated information. 14279 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. 14280 * 14281 * @param oldHolder The ViewHolder before the layout is started, might be the same 14282 * instance with newHolder. 14283 * @param newHolder The ViewHolder after the layout is finished, might be the same 14284 * instance with oldHolder. 14285 * @param preLayoutInfo The information that was returned from 14286 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14287 * @param postLayoutInfo The information that was returned from {@link 14288 * #recordPreLayoutInformation(State, ViewHolder, int, List)}. 14289 * @return true if a later call to {@link #runPendingAnimations()} is requested, 14290 * false otherwise. 14291 */ animateChange(@onNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)14292 public abstract boolean animateChange(@NonNull ViewHolder oldHolder, 14293 @NonNull ViewHolder newHolder, 14294 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); 14295 14296 @AdapterChanges buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder)14297 static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { 14298 int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); 14299 if (viewHolder.isInvalid()) { 14300 return FLAG_INVALIDATED; 14301 } 14302 if ((flags & FLAG_INVALIDATED) == 0) { 14303 final int oldPos = viewHolder.getOldPosition(); 14304 final int pos = viewHolder.getAbsoluteAdapterPosition(); 14305 if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) { 14306 flags |= FLAG_MOVED; 14307 } 14308 } 14309 return flags; 14310 } 14311 14312 /** 14313 * Called when there are pending animations waiting to be started. This state 14314 * is governed by the return values from 14315 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14316 * animateAppearance()}, 14317 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14318 * animateChange()} 14319 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14320 * animatePersistence()}, and 14321 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14322 * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be 14323 * called later to start the associated animations. runPendingAnimations() will be scheduled 14324 * to be run on the next frame. 14325 */ runPendingAnimations()14326 public abstract void runPendingAnimations(); 14327 14328 /** 14329 * Method called when an animation on a view should be ended immediately. 14330 * This could happen when other events, like scrolling, occur, so that 14331 * animating views can be quickly put into their proper end locations. 14332 * Implementations should ensure that any animations running on the item 14333 * are canceled and affected properties are set to their end values. 14334 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 14335 * animation since the animations are effectively done when this method is called. 14336 * 14337 * @param item The item for which an animation should be stopped. 14338 */ endAnimation(@onNull ViewHolder item)14339 public abstract void endAnimation(@NonNull ViewHolder item); 14340 14341 /** 14342 * Method called when all item animations should be ended immediately. 14343 * This could happen when other events, like scrolling, occur, so that 14344 * animating views can be quickly put into their proper end locations. 14345 * Implementations should ensure that any animations running on any items 14346 * are canceled and affected properties are set to their end values. 14347 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished 14348 * animation since the animations are effectively done when this method is called. 14349 */ endAnimations()14350 public abstract void endAnimations(); 14351 14352 /** 14353 * Method which returns whether there are any item animations currently running. 14354 * This method can be used to determine whether to delay other actions until 14355 * animations end. 14356 * 14357 * @return true if there are any item animations currently running, false otherwise. 14358 */ isRunning()14359 public abstract boolean isRunning(); 14360 14361 /** 14362 * Method to be called by subclasses when an animation is finished. 14363 * <p> 14364 * For each call RecyclerView makes to 14365 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14366 * animateAppearance()}, 14367 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14368 * animatePersistence()}, or 14369 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14370 * animateDisappearance()}, there 14371 * should 14372 * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. 14373 * <p> 14374 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14375 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 14376 * and <code>newHolder</code> (if they are not the same instance). 14377 * 14378 * @param viewHolder The ViewHolder whose animation is finished. 14379 * @see #onAnimationFinished(ViewHolder) 14380 */ dispatchAnimationFinished(@onNull ViewHolder viewHolder)14381 public final void dispatchAnimationFinished(@NonNull ViewHolder viewHolder) { 14382 onAnimationFinished(viewHolder); 14383 if (mListener != null) { 14384 mListener.onAnimationFinished(viewHolder); 14385 } 14386 } 14387 14388 /** 14389 * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the 14390 * ItemAnimator. 14391 * 14392 * @param viewHolder The ViewHolder whose animation is finished. There might still be other 14393 * animations running on this ViewHolder. 14394 * @see #dispatchAnimationFinished(ViewHolder) 14395 */ onAnimationFinished(@onNull ViewHolder viewHolder)14396 public void onAnimationFinished(@NonNull ViewHolder viewHolder) { 14397 } 14398 14399 /** 14400 * Method to be called by subclasses when an animation is started. 14401 * <p> 14402 * For each call RecyclerView makes to 14403 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14404 * animateAppearance()}, 14405 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14406 * animatePersistence()}, or 14407 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 14408 * animateDisappearance()}, there should be a matching 14409 * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. 14410 * <p> 14411 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 14412 * animateChange()}, subclass should call this method for both the <code>oldHolder</code> 14413 * and <code>newHolder</code> (if they are not the same instance). 14414 * <p> 14415 * If your ItemAnimator decides not to animate a ViewHolder, it should call 14416 * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling 14417 * {@link #dispatchAnimationStarted(ViewHolder)}. 14418 * 14419 * @param viewHolder The ViewHolder whose animation is starting. 14420 * @see #onAnimationStarted(ViewHolder) 14421 */ dispatchAnimationStarted(@onNull ViewHolder viewHolder)14422 public final void dispatchAnimationStarted(@NonNull ViewHolder viewHolder) { 14423 onAnimationStarted(viewHolder); 14424 } 14425 14426 /** 14427 * Called when a new animation is started on the given ViewHolder. 14428 * 14429 * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder 14430 * might already be animating and this might be another animation. 14431 * @see #dispatchAnimationStarted(ViewHolder) 14432 */ onAnimationStarted(@onNull ViewHolder viewHolder)14433 public void onAnimationStarted(@NonNull ViewHolder viewHolder) { 14434 14435 } 14436 14437 /** 14438 * Like {@link #isRunning()}, this method returns whether there are any item 14439 * animations currently running. Additionally, the listener passed in will be called 14440 * when there are no item animations running, either immediately (before the method 14441 * returns) if no animations are currently running, or when the currently running 14442 * animations are {@link #dispatchAnimationsFinished() finished}. 14443 * 14444 * <p>Note that the listener is transient - it is either called immediately and not 14445 * stored at all, or stored only until it is called when running animations 14446 * are finished sometime later.</p> 14447 * 14448 * @param listener A listener to be called immediately if no animations are running 14449 * or later when currently-running animations have finished. A null 14450 * listener is 14451 * equivalent to calling {@link #isRunning()}. 14452 * @return true if there are any item animations currently running, false otherwise. 14453 */ isRunning(@ullable ItemAnimatorFinishedListener listener)14454 public final boolean isRunning(@Nullable ItemAnimatorFinishedListener listener) { 14455 boolean running = isRunning(); 14456 if (listener != null) { 14457 if (!running) { 14458 listener.onAnimationsFinished(); 14459 } else { 14460 mFinishedListeners.add(listener); 14461 } 14462 } 14463 return running; 14464 } 14465 14466 /** 14467 * When an item is changed, ItemAnimator can decide whether it wants to re-use 14468 * the same ViewHolder for animations or RecyclerView should create a copy of the 14469 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 14470 * <p> 14471 * Note that this method will only be called if the {@link ViewHolder} still has the same 14472 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 14473 * both {@link ViewHolder}s in the 14474 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 14475 * <p> 14476 * If your application is using change payloads, you can override 14477 * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. 14478 * 14479 * @param viewHolder The ViewHolder which represents the changed item's old content. 14480 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 14481 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 14482 * ItemAnimator to animate. Default implementation returns <code>true</code>. 14483 * @see #canReuseUpdatedViewHolder(ViewHolder, List) 14484 */ canReuseUpdatedViewHolder(@onNull ViewHolder viewHolder)14485 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { 14486 return true; 14487 } 14488 14489 /** 14490 * When an item is changed, ItemAnimator can decide whether it wants to re-use 14491 * the same ViewHolder for animations or RecyclerView should create a copy of the 14492 * item and ItemAnimator will use both to run the animation (e.g. cross-fade). 14493 * <p> 14494 * Note that this method will only be called if the {@link ViewHolder} still has the same 14495 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive 14496 * both {@link ViewHolder}s in the 14497 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. 14498 * 14499 * @param viewHolder The ViewHolder which represents the changed item's old content. 14500 * @param payloads A non-null list of merged payloads that were sent with change 14501 * notifications. Can be empty if the adapter is invalidated via 14502 * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of 14503 * payloads will be passed into 14504 * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} 14505 * method <b>if</b> this method returns <code>true</code>. 14506 * @return True if RecyclerView should just rebind to the same ViewHolder or false if 14507 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the 14508 * ItemAnimator to animate. Default implementation calls 14509 * {@link #canReuseUpdatedViewHolder(ViewHolder)}. 14510 * @see #canReuseUpdatedViewHolder(ViewHolder) 14511 */ canReuseUpdatedViewHolder(@onNull ViewHolder viewHolder, @NonNull List<Object> payloads)14512 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, 14513 @NonNull List<Object> payloads) { 14514 return canReuseUpdatedViewHolder(viewHolder); 14515 } 14516 14517 /** 14518 * This method should be called by ItemAnimator implementations to notify 14519 * any listeners that all pending and active item animations are finished. 14520 */ dispatchAnimationsFinished()14521 public final void dispatchAnimationsFinished() { 14522 final int count = mFinishedListeners.size(); 14523 for (int i = 0; i < count; ++i) { 14524 mFinishedListeners.get(i).onAnimationsFinished(); 14525 } 14526 mFinishedListeners.clear(); 14527 } 14528 14529 /** 14530 * Returns a new {@link ItemHolderInfo} which will be used to store information about the 14531 * ViewHolder. This information will later be passed into <code>animate**</code> methods. 14532 * <p> 14533 * You can override this method if you want to extend {@link ItemHolderInfo} and provide 14534 * your own instances. 14535 * 14536 * @return A new {@link ItemHolderInfo}. 14537 */ obtainHolderInfo()14538 public @NonNull ItemHolderInfo obtainHolderInfo() { 14539 return new ItemHolderInfo(); 14540 } 14541 14542 /** 14543 * The interface to be implemented by listeners to animation events from this 14544 * ItemAnimator. This is used internally and is not intended for developers to 14545 * create directly. 14546 */ 14547 interface ItemAnimatorListener { onAnimationFinished(@onNull ViewHolder item)14548 void onAnimationFinished(@NonNull ViewHolder item); 14549 } 14550 14551 /** 14552 * This interface is used to inform listeners when all pending or running animations 14553 * in an ItemAnimator are finished. This can be used, for example, to delay an action 14554 * in a data set until currently-running animations are complete. 14555 * 14556 * @see #isRunning(ItemAnimatorFinishedListener) 14557 */ 14558 public interface ItemAnimatorFinishedListener { 14559 /** 14560 * Notifies when all pending or running animations in an ItemAnimator are finished. 14561 */ onAnimationsFinished()14562 void onAnimationsFinished(); 14563 } 14564 14565 /** 14566 * A simple data structure that holds information about an item's bounds. 14567 * This information is used in calculating item animations. Default implementation of 14568 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and 14569 * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data 14570 * structure. You can extend this class if you would like to keep more information about 14571 * the Views. 14572 * <p> 14573 * If you want to provide your own implementation but still use `super` methods to record 14574 * basic information, you can override {@link #obtainHolderInfo()} to provide your own 14575 * instances. 14576 */ 14577 public static class ItemHolderInfo { 14578 14579 /** 14580 * The left edge of the View (excluding decorations) 14581 */ 14582 public int left; 14583 14584 /** 14585 * The top edge of the View (excluding decorations) 14586 */ 14587 public int top; 14588 14589 /** 14590 * The right edge of the View (excluding decorations) 14591 */ 14592 public int right; 14593 14594 /** 14595 * The bottom edge of the View (excluding decorations) 14596 */ 14597 public int bottom; 14598 14599 /** 14600 * The change flags that were passed to 14601 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. 14602 */ 14603 @AdapterChanges 14604 public int changeFlags; 14605 ItemHolderInfo()14606 public ItemHolderInfo() { 14607 } 14608 14609 /** 14610 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 14611 * the given ViewHolder. Clears all {@link #changeFlags}. 14612 * 14613 * @param holder The ViewHolder whose bounds should be copied. 14614 * @return This {@link ItemHolderInfo} 14615 */ setFrom(RecyclerView.@onNull ViewHolder holder)14616 public @NonNull ItemHolderInfo setFrom(RecyclerView.@NonNull ViewHolder holder) { 14617 return setFrom(holder, 0); 14618 } 14619 14620 /** 14621 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from 14622 * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. 14623 * 14624 * @param holder The ViewHolder whose bounds should be copied. 14625 * @param flags The adapter change flags that were passed into 14626 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, 14627 * List)}. 14628 * @return This {@link ItemHolderInfo} 14629 */ setFrom(RecyclerView.@onNull ViewHolder holder, @AdapterChanges int flags)14630 public @NonNull ItemHolderInfo setFrom(RecyclerView.@NonNull ViewHolder holder, 14631 @AdapterChanges int flags) { 14632 final View view = holder.itemView; 14633 this.left = view.getLeft(); 14634 this.top = view.getTop(); 14635 this.right = view.getRight(); 14636 this.bottom = view.getBottom(); 14637 return this; 14638 } 14639 } 14640 } 14641 14642 @Override getChildDrawingOrder(int childCount, int i)14643 protected int getChildDrawingOrder(int childCount, int i) { 14644 if (mChildDrawingOrderCallback == null) { 14645 return super.getChildDrawingOrder(childCount, i); 14646 } else { 14647 return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i); 14648 } 14649 } 14650 14651 /** 14652 * A callback interface that can be used to alter the drawing order of RecyclerView children. 14653 * <p> 14654 * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case 14655 * that applies to that method also applies to this callback. For example, changing the drawing 14656 * order of two views will not have any effect if their elevation values are different since 14657 * elevation overrides the result of this callback. 14658 */ 14659 public interface ChildDrawingOrderCallback { 14660 /** 14661 * Returns the index of the child to draw for this iteration. Override this 14662 * if you want to change the drawing order of children. By default, it 14663 * returns i. 14664 * 14665 * @param childCount The total number of children. 14666 * @param i The current iteration. 14667 * @return The index of the child to draw this iteration. 14668 * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) 14669 */ onGetChildDrawingOrder(int childCount, int i)14670 int onGetChildDrawingOrder(int childCount, int i); 14671 } 14672 getScrollingChildHelper()14673 private NestedScrollingChildHelper getScrollingChildHelper() { 14674 if (mScrollingChildHelper == null) { 14675 mScrollingChildHelper = new NestedScrollingChildHelper(this); 14676 } 14677 return mScrollingChildHelper; 14678 } 14679 getScrollFeedbackProvider()14680 private ScrollFeedbackProviderCompat getScrollFeedbackProvider() { 14681 if (mScrollFeedbackProvider == null) { 14682 mScrollFeedbackProvider = ScrollFeedbackProviderCompat.createProvider(this); 14683 } 14684 return mScrollFeedbackProvider; 14685 } 14686 14687 @RequiresApi(35) 14688 private static final class Api35Impl { 14689 @DoNotInline setFrameContentVelocity(View view, float velocity)14690 public static void setFrameContentVelocity(View view, float velocity) { 14691 try { 14692 view.setFrameContentVelocity(velocity); 14693 } catch (LinkageError e) { 14694 // The setFrameContentVelocity method is unavailable on this device. 14695 } 14696 } 14697 } 14698 } 14699