blob: d7a01c4762f1d52500d66e0cf154c345c46a2457 [file] [log] [blame]
Aurimas Liutikas7149a632017-01-18 17:36:10 -08001/*
2 * Copyright (C) 2017 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
17package com.android.internal.widget;
18
19import android.annotation.CallSuper;
20import android.annotation.IntDef;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
Artur Satayev00f8b532019-12-10 17:47:55 +000023import android.compat.annotation.UnsupportedAppUsage;
Aurimas Liutikas7149a632017-01-18 17:36:10 -080024import android.content.Context;
25import android.content.res.TypedArray;
26import android.database.Observable;
27import android.graphics.Canvas;
28import android.graphics.Matrix;
29import android.graphics.PointF;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.os.Build;
33import android.os.Bundle;
34import android.os.Parcel;
35import android.os.Parcelable;
36import android.os.SystemClock;
37import android.os.Trace;
38import android.util.AttributeSet;
39import android.util.Log;
40import android.util.SparseArray;
41import android.util.TypedValue;
42import android.view.AbsSavedState;
43import android.view.Display;
44import android.view.FocusFinder;
45import android.view.InputDevice;
46import android.view.MotionEvent;
47import android.view.VelocityTracker;
48import android.view.View;
49import android.view.ViewConfiguration;
50import android.view.ViewGroup;
51import android.view.ViewParent;
52import android.view.accessibility.AccessibilityEvent;
53import android.view.accessibility.AccessibilityManager;
54import android.view.accessibility.AccessibilityNodeInfo;
55import android.view.animation.Interpolator;
56import android.widget.EdgeEffect;
57import android.widget.OverScroller;
58
59import com.android.internal.R;
60import com.android.internal.annotations.VisibleForTesting;
61import com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
62
63import java.lang.annotation.Retention;
64import java.lang.annotation.RetentionPolicy;
65import java.lang.ref.WeakReference;
66import java.lang.reflect.Constructor;
67import java.lang.reflect.InvocationTargetException;
68import java.util.ArrayList;
69import java.util.Collections;
70import java.util.List;
71
72/**
73 * A flexible view for providing a limited window into a large data set.
74 *
75 * <h3>Glossary of terms:</h3>
76 *
77 * <ul>
78 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
79 * that represent items in a data set.</li>
80 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
81 * <li><em>Index:</em> The index of an attached child view as used in a call to
82 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
83 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding
84 * to a <em>position</em> within the adapter.</li>
85 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
86 * position may be placed in a cache for later reuse to display the same type of data again
87 * later. This can drastically improve performance by skipping initial layout inflation
88 * or construction.</li>
89 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
90 * state during layout. Scrap views may be reused without becoming fully detached
91 * from the parent RecyclerView, either unmodified if no rebinding is required or modified
92 * by the adapter if the view was considered <em>dirty</em>.</li>
93 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
94 * being displayed.</li>
95 * </ul>
96 *
97 * <h4>Positions in RecyclerView:</h4>
98 * <p>
99 * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
100 * {@link LayoutManager} to be able to detect data set changes in batches during a layout
101 * calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
102 * It also helps with performance because all view bindings happen at the same time and unnecessary
103 * bindings are avoided.
104 * <p>
105 * For this reason, there are two types of <code>position</code> related methods in RecyclerView:
106 * <ul>
107 * <li>layout position: Position of an item in the latest layout calculation. This is the
108 * position from the LayoutManager's perspective.</li>
109 * <li>adapter position: Position of an item in the adapter. This is the position from
110 * the Adapter's perspective.</li>
111 * </ul>
112 * <p>
113 * These two positions are the same except the time between dispatching <code>adapter.notify*
114 * </code> events and calculating the updated layout.
115 * <p>
116 * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest
117 * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
118 * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
119 * last layout calculation. You can rely on these positions to be consistent with what user is
120 * currently seeing on the screen. For example, if you have a list of items on the screen and user
121 * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user
122 * is seeing.
123 * <p>
124 * The other set of position related methods are in the form of
125 * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
126 * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
127 * work with up-to-date adapter positions even if they may not have been reflected to layout yet.
128 * For example, if you want to access the item in the adapter on a ViewHolder click, you should use
129 * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
130 * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
131 * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
132 * <code>null</code> results from these methods.
133 * <p>
134 * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
135 * writing an {@link Adapter}, you probably want to use adapter positions.
Aurimas Liutikas7149a632017-01-18 17:36:10 -0800136 */
137public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
138
139 static final String TAG = "RecyclerView";
140
141 static final boolean DEBUG = false;
142
143 private static final int[] NESTED_SCROLLING_ATTRS = { android.R.attr.nestedScrollingEnabled };
144
145 private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding};
146
147 /**
148 * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
149 * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
150 * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler
151 * recursively traverses itemView and invalidates display list for each ViewGroup that matches
152 * this criteria.
153 */
154 static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
155 || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
156 /**
157 * On M+, an unspecified measure spec may include a hint which we can use. On older platforms,
158 * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to
159 * 0 when mode is unspecified.
160 */
161 static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23;
162
163 static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16;
164
165 /**
166 * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to
167 * RenderThread but before the next frame begins. We schedule prefetch work in this window.
168 */
169 private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
170
171 /**
172 * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction.
173 * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT.
174 */
175 private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
176
177 /**
178 * on API 15-, a focused child can still be considered a focused child of RV even after
179 * it's being removed or its focusable flag is set to false. This is because when this focused
180 * child is detached, the reference to this child is not removed in clearFocus. API 16 and above
181 * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
182 * to request focus on a new child, which will clear the focus on the old (detached) child as a
183 * side-effect.
184 */
185 private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
186
187 static final boolean DISPATCH_TEMP_DETACH = false;
188 public static final int HORIZONTAL = 0;
189 public static final int VERTICAL = 1;
190
191 public static final int NO_POSITION = -1;
192 public static final long NO_ID = -1;
193 public static final int INVALID_TYPE = -1;
194
195 /**
196 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
197 * that the RecyclerView should use the standard touch slop for smooth,
198 * continuous scrolling.
199 */
200 public static final int TOUCH_SLOP_DEFAULT = 0;
201
202 /**
203 * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
204 * that the RecyclerView should use the standard touch slop for scrolling
205 * widgets that snap to a page or other coarse-grained barrier.
206 */
207 public static final int TOUCH_SLOP_PAGING = 1;
208
209 static final int MAX_SCROLL_DURATION = 2000;
210
211 /**
212 * RecyclerView is calculating a scroll.
213 * If there are too many of these in Systrace, some Views inside RecyclerView might be causing
214 * it. Try to avoid using EditText, focusable views or handle them with care.
215 */
216 static final String TRACE_SCROLL_TAG = "RV Scroll";
217
218 /**
219 * OnLayout has been called by the View system.
220 * If this shows up too many times in Systrace, make sure the children of RecyclerView do not
221 * update themselves directly. This will cause a full re-layout but when it happens via the
222 * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.
223 */
224 private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout";
225
226 /**
227 * NotifyDataSetChanged or equal has been called.
228 * If this is taking a long time, try sending granular notify adapter changes instead of just
229 * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter
230 * might help.
231 */
232 private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate";
233
234 /**
235 * RecyclerView is doing a layout for partial adapter updates (we know what has changed)
236 * If this is taking a long time, you may have dispatched too many Adapter updates causing too
237 * many Views being rebind. Make sure all are necessary and also prefer using notify*Range
238 * methods.
239 */
240 private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate";
241
242 /**
243 * RecyclerView is rebinding a View.
244 * If this is taking a lot of time, consider optimizing your layout or make sure you are not
245 * doing extra operations in onBindViewHolder call.
246 */
247 static final String TRACE_BIND_VIEW_TAG = "RV OnBindView";
248
249 /**
250 * RecyclerView is attempting to pre-populate off screen views.
251 */
252 static final String TRACE_PREFETCH_TAG = "RV Prefetch";
253
254 /**
255 * RecyclerView is attempting to pre-populate off screen itemviews within an off screen
256 * RecyclerView.
257 */
258 static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch";
259
260 /**
261 * RecyclerView is creating a new View.
262 * If too many of these present in Systrace:
263 * - There might be a problem in Recycling (e.g. custom Animations that set transient state and
264 * prevent recycling or ItemAnimator not implementing the contract properly. ({@link
265 * > Adapter#onFailedToRecycleView(ViewHolder)})
266 *
267 * - There might be too many item view types.
268 * > Try merging them
269 *
270 * - There might be too many itemChange animations and not enough space in RecyclerPool.
271 * >Try increasing your pool size and item cache size.
272 */
273 static final String TRACE_CREATE_VIEW_TAG = "RV CreateView";
274 private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
275 new Class[]{Context.class, AttributeSet.class, int.class, int.class};
276
277 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
278
279 final Recycler mRecycler = new Recycler();
280
281 private SavedState mPendingSavedState;
282
283 /**
284 * Handles adapter updates
285 */
286 AdapterHelper mAdapterHelper;
287
288 /**
289 * Handles abstraction between LayoutManager children and RecyclerView children
290 */
291 ChildHelper mChildHelper;
292
293 /**
294 * Keeps data about views to be used for animations
295 */
296 final ViewInfoStore mViewInfoStore = new ViewInfoStore();
297
298 /**
299 * Prior to L, there is no way to query this variable which is why we override the setter and
300 * track it here.
301 */
302 boolean mClipToPadding;
303
304 /**
305 * Note: this Runnable is only ever posted if:
306 * 1) We've been through first layout
307 * 2) We know we have a fixed size (mHasFixedSize)
308 * 3) We're attached
309 */
310 final Runnable mUpdateChildViewsRunnable = new Runnable() {
311 @Override
312 public void run() {
313 if (!mFirstLayoutComplete || isLayoutRequested()) {
314 // a layout request will happen, we should not do layout here.
315 return;
316 }
317 if (!mIsAttached) {
318 requestLayout();
319 // if we are not attached yet, mark us as requiring layout and skip
320 return;
321 }
322 if (mLayoutFrozen) {
323 mLayoutRequestEaten = true;
324 return; //we'll process updates when ice age ends.
325 }
326 consumePendingUpdateOperations();
327 }
328 };
329
330 final Rect mTempRect = new Rect();
331 private final Rect mTempRect2 = new Rect();
332 final RectF mTempRectF = new RectF();
333 Adapter mAdapter;
334 @VisibleForTesting LayoutManager mLayout;
335 RecyclerListener mRecyclerListener;
336 final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
337 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
338 new ArrayList<>();
339 private OnItemTouchListener mActiveOnItemTouchListener;
340 boolean mIsAttached;
341 boolean mHasFixedSize;
342 @VisibleForTesting boolean mFirstLayoutComplete;
343
344 // Counting lock to control whether we should ignore requestLayout calls from children or not.
345 private int mEatRequestLayout = 0;
346
347 boolean mLayoutRequestEaten;
348 boolean mLayoutFrozen;
349 private boolean mIgnoreMotionEventTillDown;
350
351 // binary OR of change events that were eaten during a layout or scroll.
352 private int mEatenAccessibilityChangeFlags;
353 boolean mAdapterUpdateDuringMeasure;
354
355 private final AccessibilityManager mAccessibilityManager;
356 private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
357
358 /**
359 * Set to true when an adapter data set changed notification is received.
360 * In that case, we cannot run any animations since we don't know what happened until layout.
361 *
362 * Attached items are invalid until next layout, at which point layout will animate/replace
363 * items as necessary, building up content from the (effectively) new adapter from scratch.
364 *
365 * Cached items must be discarded when setting this to true, so that the cache may be freely
366 * used by prefetching until the next layout occurs.
367 *
368 * @see #setDataSetChangedAfterLayout()
369 */
370 boolean mDataSetHasChangedAfterLayout = false;
371
372 /**
373 * This variable is incremented during a dispatchLayout and/or scroll.
374 * Some methods should not be called during these periods (e.g. adapter data change).
375 * Doing so will create hard to find bugs so we better check it and throw an exception.
376 *
377 * @see #assertInLayoutOrScroll(String)
378 * @see #assertNotInLayoutOrScroll(String)
379 */
380 private int mLayoutOrScrollCounter = 0;
381
382 /**
383 * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception
384 * (for API compatibility).
385 * <p>
386 * It is a bad practice for a developer to update the data in a scroll callback since it is
387 * potentially called during a layout.
388 */
389 private int mDispatchScrollCounter = 0;
390
391 private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
392
393 ItemAnimator mItemAnimator = new DefaultItemAnimator();
394
395 private static final int INVALID_POINTER = -1;
396
397 /**
398 * The RecyclerView is not currently scrolling.
399 * @see #getScrollState()
400 */
401 public static final int SCROLL_STATE_IDLE = 0;
402
403 /**
404 * The RecyclerView is currently being dragged by outside input such as user touch input.
405 * @see #getScrollState()
406 */
407 public static final int SCROLL_STATE_DRAGGING = 1;
408
409 /**
410 * The RecyclerView is currently animating to a final position while not under
411 * outside control.
412 * @see #getScrollState()
413 */
414 public static final int SCROLL_STATE_SETTLING = 2;
415
416 static final long FOREVER_NS = Long.MAX_VALUE;
417
418 // Touch/scrolling handling
419
420 private int mScrollState = SCROLL_STATE_IDLE;
421 private int mScrollPointerId = INVALID_POINTER;
422 private VelocityTracker mVelocityTracker;
423 private int mInitialTouchX;
424 private int mInitialTouchY;
425 private int mLastTouchX;
426 private int mLastTouchY;
427 private int mTouchSlop;
428 private OnFlingListener mOnFlingListener;
429 private final int mMinFlingVelocity;
430 private final int mMaxFlingVelocity;
431 // This value is used when handling generic motion events.
432 private float mScrollFactor = Float.MIN_VALUE;
433 private boolean mPreserveFocusAfterLayout = true;
434
435 final ViewFlinger mViewFlinger = new ViewFlinger();
436
437 GapWorker mGapWorker;
438 GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry =
439 ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null;
440
441 final State mState = new State();
442
443 private OnScrollListener mScrollListener;
444 private List<OnScrollListener> mScrollListeners;
445
446 // For use in item animations
447 boolean mItemsAddedOrRemoved = false;
448 boolean mItemsChanged = false;
449 private ItemAnimator.ItemAnimatorListener mItemAnimatorListener =
450 new ItemAnimatorRestoreListener();
451 boolean mPostedAnimatorRunner = false;
452 RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
453 private ChildDrawingOrderCallback mChildDrawingOrderCallback;
454
455 // simple array to keep min and max child position during a layout calculation
456 // preserved not to create a new one in each layout pass
457 private final int[] mMinMaxLayoutPositions = new int[2];
458
459 private final int[] mScrollOffset = new int[2];
460 private final int[] mScrollConsumed = new int[2];
461 private final int[] mNestedOffsets = new int[2];
462
463 /**
464 * These are views that had their a11y importance changed during a layout. We defer these events
465 * until the end of the layout because a11y service may make sync calls back to the RV while
466 * the View's state is undefined.
467 */
468 @VisibleForTesting
469 final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList();
470
471 private Runnable mItemAnimatorRunner = new Runnable() {
472 @Override
473 public void run() {
474 if (mItemAnimator != null) {
475 mItemAnimator.runPendingAnimations();
476 }
477 mPostedAnimatorRunner = false;
478 }
479 };
480
481 static final Interpolator sQuinticInterpolator = new Interpolator() {
482 @Override
483 public float getInterpolation(float t) {
484 t -= 1.0f;
485 return t * t * t * t * t + 1.0f;
486 }
487 };
488
489 /**
490 * The callback to convert view info diffs into animations.
491 */
492 private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
493 new ViewInfoStore.ProcessCallback() {
494 @Override
495 public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
496 @Nullable ItemHolderInfo postInfo) {
497 mRecycler.unscrapView(viewHolder);
498 animateDisappearance(viewHolder, info, postInfo);
499 }
500 @Override
501 public void processAppeared(ViewHolder viewHolder,
502 ItemHolderInfo preInfo, ItemHolderInfo info) {
503 animateAppearance(viewHolder, preInfo, info);
504 }
505
506 @Override
507 public void processPersistent(ViewHolder viewHolder,
508 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
509 viewHolder.setIsRecyclable(false);
510 if (mDataSetHasChangedAfterLayout) {
511 // since it was rebound, use change instead as we'll be mapping them from
512 // stable ids. If stable ids were false, we would not be running any
513 // animations
514 if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) {
515 postAnimationRunner();
516 }
517 } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
518 postAnimationRunner();
519 }
520 }
521 @Override
522 public void unused(ViewHolder viewHolder) {
523 mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
524 }
525 };
526
527 public RecyclerView(Context context) {
528 this(context, null);
529 }
530
531 public RecyclerView(Context context, @Nullable AttributeSet attrs) {
532 this(context, attrs, 0);
533 }
534
535 public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
536 super(context, attrs, defStyle);
537 if (attrs != null) {
538 TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
539 mClipToPadding = a.getBoolean(0, true);
540 a.recycle();
541 } else {
542 mClipToPadding = true;
543 }
544 setScrollContainer(true);
545 setFocusableInTouchMode(true);
546
547 final ViewConfiguration vc = ViewConfiguration.get(context);
548 mTouchSlop = vc.getScaledTouchSlop();
549 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
550 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
551 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
552
553 mItemAnimator.setListener(mItemAnimatorListener);
554 initAdapterManager();
555 initChildrenHelper();
556 // If not explicitly specified this view is important for accessibility.
557 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
558 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
559 }
560 mAccessibilityManager = (AccessibilityManager) getContext()
561 .getSystemService(Context.ACCESSIBILITY_SERVICE);
562 setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
563 // Create the layoutManager if specified.
564
565 boolean nestedScrollingEnabled = true;
566
567 if (attrs != null) {
568 int defStyleRes = 0;
569 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
570 defStyle, defStyleRes);
571 String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
572 int descendantFocusability = a.getInt(
573 R.styleable.RecyclerView_descendantFocusability, -1);
574 if (descendantFocusability == -1) {
575 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
576 }
577 a.recycle();
578 createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
579
580 if (Build.VERSION.SDK_INT >= 21) {
581 a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
582 defStyle, defStyleRes);
583 nestedScrollingEnabled = a.getBoolean(0, true);
584 a.recycle();
585 }
586 } else {
587 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
588 }
589
590 // Re-set whether nested scrolling is enabled so that it is set on all API levels
591 setNestedScrollingEnabled(nestedScrollingEnabled);
592 }
593
594 /**
595 * Returns the accessibility delegate compatibility implementation used by the RecyclerView.
596 * @return An instance of AccessibilityDelegateCompat used by RecyclerView
597 */
598 public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() {
599 return mAccessibilityDelegate;
600 }
601
602 /**
603 * Sets the accessibility delegate compatibility implementation used by RecyclerView.
604 * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView.
605 */
606 public void setAccessibilityDelegateCompat(
607 RecyclerViewAccessibilityDelegate accessibilityDelegate) {
608 mAccessibilityDelegate = accessibilityDelegate;
609 setAccessibilityDelegate(mAccessibilityDelegate);
610 }
611
612 /**
613 * Instantiate and set a LayoutManager, if specified in the attributes.
614 */
615 private void createLayoutManager(Context context, String className, AttributeSet attrs,
616 int defStyleAttr, int defStyleRes) {
617 if (className != null) {
618 className = className.trim();
619 if (className.length() != 0) { // Can't use isEmpty since it was added in API 9.
620 className = getFullClassName(context, className);
621 try {
622 ClassLoader classLoader;
623 if (isInEditMode()) {
624 // Stupid layoutlib cannot handle simple class loaders.
625 classLoader = this.getClass().getClassLoader();
626 } else {
627 classLoader = context.getClassLoader();
628 }
629 Class<? extends LayoutManager> layoutManagerClass =
630 classLoader.loadClass(className).asSubclass(LayoutManager.class);
631 Constructor<? extends LayoutManager> constructor;
632 Object[] constructorArgs = null;
633 try {
634 constructor = layoutManagerClass
635 .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
636 constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
637 } catch (NoSuchMethodException e) {
638 try {
639 constructor = layoutManagerClass.getConstructor();
640 } catch (NoSuchMethodException e1) {
641 e1.initCause(e);
642 throw new IllegalStateException(attrs.getPositionDescription()
643 + ": Error creating LayoutManager " + className, e1);
644 }
645 }
646 constructor.setAccessible(true);
647 setLayoutManager(constructor.newInstance(constructorArgs));
648 } catch (ClassNotFoundException e) {
649 throw new IllegalStateException(attrs.getPositionDescription()
650 + ": Unable to find LayoutManager " + className, e);
651 } catch (InvocationTargetException e) {
652 throw new IllegalStateException(attrs.getPositionDescription()
653 + ": Could not instantiate the LayoutManager: " + className, e);
654 } catch (InstantiationException e) {
655 throw new IllegalStateException(attrs.getPositionDescription()
656 + ": Could not instantiate the LayoutManager: " + className, e);
657 } catch (IllegalAccessException e) {
658 throw new IllegalStateException(attrs.getPositionDescription()
659 + ": Cannot access non-public constructor " + className, e);
660 } catch (ClassCastException e) {
661 throw new IllegalStateException(attrs.getPositionDescription()
662 + ": Class is not a LayoutManager " + className, e);
663 }
664 }
665 }
666 }
667
668 private String getFullClassName(Context context, String className) {
669 if (className.charAt(0) == '.') {
670 return context.getPackageName() + className;
671 }
672 if (className.contains(".")) {
673 return className;
674 }
675 return RecyclerView.class.getPackage().getName() + '.' + className;
676 }
677
678 private void initChildrenHelper() {
679 mChildHelper = new ChildHelper(new ChildHelper.Callback() {
680 @Override
681 public int getChildCount() {
682 return RecyclerView.this.getChildCount();
683 }
684
685 @Override
686 public void addView(View child, int index) {
687 RecyclerView.this.addView(child, index);
688 dispatchChildAttached(child);
689 }
690
691 @Override
692 public int indexOfChild(View view) {
693 return RecyclerView.this.indexOfChild(view);
694 }
695
696 @Override
697 public void removeViewAt(int index) {
698 final View child = RecyclerView.this.getChildAt(index);
699 if (child != null) {
700 dispatchChildDetached(child);
701 }
702 RecyclerView.this.removeViewAt(index);
703 }
704
705 @Override
706 public View getChildAt(int offset) {
707 return RecyclerView.this.getChildAt(offset);
708 }
709
710 @Override
711 public void removeAllViews() {
712 final int count = getChildCount();
713 for (int i = 0; i < count; i++) {
714 dispatchChildDetached(getChildAt(i));
715 }
716 RecyclerView.this.removeAllViews();
717 }
718
719 @Override
720 public ViewHolder getChildViewHolder(View view) {
721 return getChildViewHolderInt(view);
722 }
723
724 @Override
725 public void attachViewToParent(View child, int index,
726 ViewGroup.LayoutParams layoutParams) {
727 final ViewHolder vh = getChildViewHolderInt(child);
728 if (vh != null) {
729 if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
730 throw new IllegalArgumentException("Called attach on a child which is not"
731 + " detached: " + vh);
732 }
733 if (DEBUG) {
734 Log.d(TAG, "reAttach " + vh);
735 }
736 vh.clearTmpDetachFlag();
737 }
738 RecyclerView.this.attachViewToParent(child, index, layoutParams);
739 }
740
741 @Override
742 public void detachViewFromParent(int offset) {
743 final View view = getChildAt(offset);
744 if (view != null) {
745 final ViewHolder vh = getChildViewHolderInt(view);
746 if (vh != null) {
747 if (vh.isTmpDetached() && !vh.shouldIgnore()) {
748 throw new IllegalArgumentException("called detach on an already"
749 + " detached child " + vh);
750 }
751 if (DEBUG) {
752 Log.d(TAG, "tmpDetach " + vh);
753 }
754 vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
755 }
756 }
757 RecyclerView.this.detachViewFromParent(offset);
758 }
759
760 @Override
761 public void onEnteredHiddenState(View child) {
762 final ViewHolder vh = getChildViewHolderInt(child);
763 if (vh != null) {
764 vh.onEnteredHiddenState(RecyclerView.this);
765 }
766 }
767
768 @Override
769 public void onLeftHiddenState(View child) {
770 final ViewHolder vh = getChildViewHolderInt(child);
771 if (vh != null) {
772 vh.onLeftHiddenState(RecyclerView.this);
773 }
774 }
775 });
776 }
777
778 void initAdapterManager() {
779 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
780 @Override
781 public ViewHolder findViewHolder(int position) {
782 final ViewHolder vh = findViewHolderForPosition(position, true);
783 if (vh == null) {
784 return null;
785 }
786 // ensure it is not hidden because for adapter helper, the only thing matter is that
787 // LM thinks view is a child.
788 if (mChildHelper.isHidden(vh.itemView)) {
789 if (DEBUG) {
790 Log.d(TAG, "assuming view holder cannot be find because it is hidden");
791 }
792 return null;
793 }
794 return vh;
795 }
796
797 @Override
798 public void offsetPositionsForRemovingInvisible(int start, int count) {
799 offsetPositionRecordsForRemove(start, count, true);
800 mItemsAddedOrRemoved = true;
801 mState.mDeletedInvisibleItemCountSincePreviousLayout += count;
802 }
803
804 @Override
805 public void offsetPositionsForRemovingLaidOutOrNewView(
806 int positionStart, int itemCount) {
807 offsetPositionRecordsForRemove(positionStart, itemCount, false);
808 mItemsAddedOrRemoved = true;
809 }
810
811 @Override
812 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
813 viewRangeUpdate(positionStart, itemCount, payload);
814 mItemsChanged = true;
815 }
816
817 @Override
818 public void onDispatchFirstPass(AdapterHelper.UpdateOp op) {
819 dispatchUpdate(op);
820 }
821
822 void dispatchUpdate(AdapterHelper.UpdateOp op) {
823 switch (op.cmd) {
824 case AdapterHelper.UpdateOp.ADD:
825 mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
826 break;
827 case AdapterHelper.UpdateOp.REMOVE:
828 mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
829 break;
830 case AdapterHelper.UpdateOp.UPDATE:
831 mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
832 op.payload);
833 break;
834 case AdapterHelper.UpdateOp.MOVE:
835 mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
836 break;
837 }
838 }
839
840 @Override
841 public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
842 dispatchUpdate(op);
843 }
844
845 @Override
846 public void offsetPositionsForAdd(int positionStart, int itemCount) {
847 offsetPositionRecordsForInsert(positionStart, itemCount);
848 mItemsAddedOrRemoved = true;
849 }
850
851 @Override
852 public void offsetPositionsForMove(int from, int to) {
853 offsetPositionRecordsForMove(from, to);
854 // should we create mItemsMoved ?
855 mItemsAddedOrRemoved = true;
856 }
857 });
858 }
859
860 /**
861 * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
862 * size is not affected by the adapter contents. RecyclerView can still change its size based
863 * on other factors (e.g. its parent's size) but this size calculation cannot depend on the
864 * size of its children or contents of its adapter (except the number of items in the adapter).
865 * <p>
866 * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
867 * RecyclerView to avoid invalidating the whole layout when its adapter contents change.
868 *
869 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
870 */
871 public void setHasFixedSize(boolean hasFixedSize) {
872 mHasFixedSize = hasFixedSize;
873 }
874
875 /**
876 * @return true if the app has specified that changes in adapter content cannot change
877 * the size of the RecyclerView itself.
878 */
879 public boolean hasFixedSize() {
880 return mHasFixedSize;
881 }
882
883 @Override
884 public void setClipToPadding(boolean clipToPadding) {
885 if (clipToPadding != mClipToPadding) {
886 invalidateGlows();
887 }
888 mClipToPadding = clipToPadding;
889 super.setClipToPadding(clipToPadding);
890 if (mFirstLayoutComplete) {
891 requestLayout();
892 }
893 }
894
895 /**
896 * Returns whether this RecyclerView will clip its children to its padding, and resize (but
897 * not clip) any EdgeEffect to the padded region, if padding is present.
898 * <p>
899 * By default, children are clipped to the padding of their parent
900 * RecyclerView. This clipping behavior is only enabled if padding is non-zero.
901 *
902 * @return true if this RecyclerView clips children to its padding and resizes (but doesn't
903 * clip) any EdgeEffect to the padded region, false otherwise.
904 *
905 * @attr name android:clipToPadding
906 */
907 @Override
908 public boolean getClipToPadding() {
909 return mClipToPadding;
910 }
911
912 /**
913 * Configure the scrolling touch slop for a specific use case.
914 *
915 * Set up the RecyclerView's scrolling motion threshold based on common usages.
916 * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
917 *
918 * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing
919 * the intended usage of this RecyclerView
920 */
921 public void setScrollingTouchSlop(int slopConstant) {
922 final ViewConfiguration vc = ViewConfiguration.get(getContext());
923 switch (slopConstant) {
924 default:
925 Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
926 + slopConstant + "; using default value");
927 // fall-through
928 case TOUCH_SLOP_DEFAULT:
929 mTouchSlop = vc.getScaledTouchSlop();
930 break;
931
932 case TOUCH_SLOP_PAGING:
933 mTouchSlop = vc.getScaledPagingTouchSlop();
934 break;
935 }
936 }
937
938 /**
939 * Swaps the current adapter with the provided one. It is similar to
940 * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
941 * {@link ViewHolder} and does not clear the RecycledViewPool.
942 * <p>
943 * Note that it still calls onAdapterChanged callbacks.
944 *
945 * @param adapter The new adapter to set, or null to set no adapter.
946 * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing
947 * Views. If adapters have stable ids and/or you want to
948 * animate the disappearing views, you may prefer to set
949 * this to false.
950 * @see #setAdapter(Adapter)
951 */
952 public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
953 // bail out if layout is frozen
954 setLayoutFrozen(false);
955 setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
956 setDataSetChangedAfterLayout();
957 requestLayout();
958 }
959 /**
960 * Set a new adapter to provide child views on demand.
961 * <p>
962 * When adapter is changed, all existing views are recycled back to the pool. If the pool has
963 * only one adapter, it will be cleared.
964 *
965 * @param adapter The new adapter to set, or null to set no adapter.
966 * @see #swapAdapter(Adapter, boolean)
967 */
968 public void setAdapter(Adapter adapter) {
969 // bail out if layout is frozen
970 setLayoutFrozen(false);
971 setAdapterInternal(adapter, false, true);
972 requestLayout();
973 }
974
975 /**
976 * Removes and recycles all views - both those currently attached, and those in the Recycler.
977 */
978 void removeAndRecycleViews() {
979 // end all running animations
980 if (mItemAnimator != null) {
981 mItemAnimator.endAnimations();
982 }
983 // Since animations are ended, mLayout.children should be equal to
984 // recyclerView.children. This may not be true if item animator's end does not work as
985 // expected. (e.g. not release children instantly). It is safer to use mLayout's child
986 // count.
987 if (mLayout != null) {
988 mLayout.removeAndRecycleAllViews(mRecycler);
989 mLayout.removeAndRecycleScrapInt(mRecycler);
990 }
991 // we should clear it here before adapters are swapped to ensure correct callbacks.
992 mRecycler.clear();
993 }
994
995 /**
996 * Replaces the current adapter with the new one and triggers listeners.
997 * @param adapter The new adapter
998 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
999 * item types with the current adapter (helps us avoid cache
1000 * invalidation).
1001 * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
1002 * compatibleWithPrevious is false, this parameter is ignored.
1003 */
1004 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
1005 boolean removeAndRecycleViews) {
1006 if (mAdapter != null) {
1007 mAdapter.unregisterAdapterDataObserver(mObserver);
1008 mAdapter.onDetachedFromRecyclerView(this);
1009 }
1010 if (!compatibleWithPrevious || removeAndRecycleViews) {
1011 removeAndRecycleViews();
1012 }
1013 mAdapterHelper.reset();
1014 final Adapter oldAdapter = mAdapter;
1015 mAdapter = adapter;
1016 if (adapter != null) {
1017 adapter.registerAdapterDataObserver(mObserver);
1018 adapter.onAttachedToRecyclerView(this);
1019 }
1020 if (mLayout != null) {
1021 mLayout.onAdapterChanged(oldAdapter, mAdapter);
1022 }
1023 mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
1024 mState.mStructureChanged = true;
1025 markKnownViewsInvalid();
1026 }
1027
1028 /**
1029 * Retrieves the previously set adapter or null if no adapter is set.
1030 *
1031 * @return The previously set adapter
1032 * @see #setAdapter(Adapter)
1033 */
1034 public Adapter getAdapter() {
1035 return mAdapter;
1036 }
1037
1038 /**
1039 * Register a listener that will be notified whenever a child view is recycled.
1040 *
1041 * <p>This listener will be called when a LayoutManager or the RecyclerView decides
1042 * that a child view is no longer needed. If an application associates expensive
1043 * or heavyweight data with item views, this may be a good place to release
1044 * or free those resources.</p>
1045 *
1046 * @param listener Listener to register, or null to clear
1047 */
1048 public void setRecyclerListener(RecyclerListener listener) {
1049 mRecyclerListener = listener;
1050 }
1051
1052 /**
1053 * <p>Return the offset of the RecyclerView's text baseline from the its top
1054 * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment,
1055 * this method returns -1.</p>
1056 *
1057 * @return the offset of the baseline within the RecyclerView's bounds or -1
1058 * if baseline alignment is not supported
1059 */
1060 @Override
1061 public int getBaseline() {
1062 if (mLayout != null) {
1063 return mLayout.getBaseline();
1064 } else {
1065 return super.getBaseline();
1066 }
1067 }
1068
1069 /**
1070 * Register a listener that will be notified whenever a child view is attached to or detached
1071 * from RecyclerView.
1072 *
1073 * <p>This listener will be called when a LayoutManager or the RecyclerView decides
1074 * that a child view is no longer needed. If an application associates expensive
1075 * or heavyweight data with item views, this may be a good place to release
1076 * or free those resources.</p>
1077 *
1078 * @param listener Listener to register
1079 */
1080 public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
1081 if (mOnChildAttachStateListeners == null) {
1082 mOnChildAttachStateListeners = new ArrayList<>();
1083 }
1084 mOnChildAttachStateListeners.add(listener);
1085 }
1086
1087 /**
1088 * Removes the provided listener from child attached state listeners list.
1089 *
1090 * @param listener Listener to unregister
1091 */
1092 public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
1093 if (mOnChildAttachStateListeners == null) {
1094 return;
1095 }
1096 mOnChildAttachStateListeners.remove(listener);
1097 }
1098
1099 /**
1100 * Removes all listeners that were added via
1101 * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}.
1102 */
1103 public void clearOnChildAttachStateChangeListeners() {
1104 if (mOnChildAttachStateListeners != null) {
1105 mOnChildAttachStateListeners.clear();
1106 }
1107 }
1108
1109 /**
1110 * Set the {@link LayoutManager} that this RecyclerView will use.
1111 *
1112 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
1113 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
1114 * layout arrangements for child views. These arrangements are controlled by the
1115 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
1116 *
1117 * <p>Several default strategies are provided for common uses such as lists and grids.</p>
1118 *
1119 * @param layout LayoutManager to use
1120 */
1121 public void setLayoutManager(LayoutManager layout) {
1122 if (layout == mLayout) {
1123 return;
1124 }
1125 stopScroll();
1126 // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
1127 // chance that LayoutManagers will re-use views.
1128 if (mLayout != null) {
1129 // end all running animations
1130 if (mItemAnimator != null) {
1131 mItemAnimator.endAnimations();
1132 }
1133 mLayout.removeAndRecycleAllViews(mRecycler);
1134 mLayout.removeAndRecycleScrapInt(mRecycler);
1135 mRecycler.clear();
1136
1137 if (mIsAttached) {
1138 mLayout.dispatchDetachedFromWindow(this, mRecycler);
1139 }
1140 mLayout.setRecyclerView(null);
1141 mLayout = null;
1142 } else {
1143 mRecycler.clear();
1144 }
1145 // this is just a defensive measure for faulty item animators.
1146 mChildHelper.removeAllViewsUnfiltered();
1147 mLayout = layout;
1148 if (layout != null) {
1149 if (layout.mRecyclerView != null) {
1150 throw new IllegalArgumentException("LayoutManager " + layout
1151 + " is already attached to a RecyclerView: " + layout.mRecyclerView);
1152 }
1153 mLayout.setRecyclerView(this);
1154 if (mIsAttached) {
1155 mLayout.dispatchAttachedToWindow(this);
1156 }
1157 }
1158 mRecycler.updateViewCacheSize();
1159 requestLayout();
1160 }
1161
1162 /**
1163 * Set a {@link OnFlingListener} for this {@link RecyclerView}.
1164 * <p>
1165 * If the {@link OnFlingListener} is set then it will receive
1166 * calls to {@link #fling(int,int)} and will be able to intercept them.
1167 *
1168 * @param onFlingListener The {@link OnFlingListener} instance.
1169 */
1170 public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) {
1171 mOnFlingListener = onFlingListener;
1172 }
1173
1174 /**
1175 * Get the current {@link OnFlingListener} from this {@link RecyclerView}.
1176 *
1177 * @return The {@link OnFlingListener} instance currently set (can be null).
1178 */
1179 @Nullable
1180 public OnFlingListener getOnFlingListener() {
1181 return mOnFlingListener;
1182 }
1183
1184 @Override
1185 protected Parcelable onSaveInstanceState() {
1186 SavedState state = new SavedState(super.onSaveInstanceState());
1187 if (mPendingSavedState != null) {
1188 state.copyFrom(mPendingSavedState);
1189 } else if (mLayout != null) {
1190 state.mLayoutState = mLayout.onSaveInstanceState();
1191 } else {
1192 state.mLayoutState = null;
1193 }
1194
1195 return state;
1196 }
1197
1198 @Override
1199 protected void onRestoreInstanceState(Parcelable state) {
1200 if (!(state instanceof SavedState)) {
1201 super.onRestoreInstanceState(state);
1202 return;
1203 }
1204
1205 mPendingSavedState = (SavedState) state;
1206 super.onRestoreInstanceState(mPendingSavedState.getSuperState());
1207 if (mLayout != null && mPendingSavedState.mLayoutState != null) {
1208 mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
1209 }
1210 }
1211
1212 /**
1213 * Override to prevent freezing of any views created by the adapter.
1214 */
1215 @Override
1216 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
1217 dispatchFreezeSelfOnly(container);
1218 }
1219
1220 /**
1221 * Override to prevent thawing of any views created by the adapter.
1222 */
1223 @Override
1224 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
1225 dispatchThawSelfOnly(container);
1226 }
1227
1228 /**
1229 * Adds a view to the animatingViews list.
1230 * mAnimatingViews holds the child views that are currently being kept around
1231 * purely for the purpose of being animated out of view. They are drawn as a regular
1232 * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
1233 * as they are managed separately from the regular child views.
1234 * @param viewHolder The ViewHolder to be removed
1235 */
1236 private void addAnimatingView(ViewHolder viewHolder) {
1237 final View view = viewHolder.itemView;
1238 final boolean alreadyParented = view.getParent() == this;
1239 mRecycler.unscrapView(getChildViewHolder(view));
1240 if (viewHolder.isTmpDetached()) {
1241 // re-attach
1242 mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
1243 } else if (!alreadyParented) {
1244 mChildHelper.addView(view, true);
1245 } else {
1246 mChildHelper.hide(view);
1247 }
1248 }
1249
1250 /**
1251 * Removes a view from the animatingViews list.
1252 * @param view The view to be removed
1253 * @see #addAnimatingView(RecyclerView.ViewHolder)
1254 * @return true if an animating view is removed
1255 */
1256 boolean removeAnimatingView(View view) {
1257 eatRequestLayout();
1258 final boolean removed = mChildHelper.removeViewIfHidden(view);
1259 if (removed) {
1260 final ViewHolder viewHolder = getChildViewHolderInt(view);
1261 mRecycler.unscrapView(viewHolder);
1262 mRecycler.recycleViewHolderInternal(viewHolder);
1263 if (DEBUG) {
1264 Log.d(TAG, "after removing animated view: " + view + ", " + this);
1265 }
1266 }
1267 // only clear request eaten flag if we removed the view.
1268 resumeRequestLayout(!removed);
1269 return removed;
1270 }
1271
1272 /**
1273 * Return the {@link LayoutManager} currently responsible for
1274 * layout policy for this RecyclerView.
1275 *
1276 * @return The currently bound LayoutManager
1277 */
1278 public LayoutManager getLayoutManager() {
1279 return mLayout;
1280 }
1281
1282 /**
1283 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
1284 * if no pool is set for this view a new one will be created. See
1285 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
1286 *
1287 * @return The pool used to store recycled item views for reuse.
1288 * @see #setRecycledViewPool(RecycledViewPool)
1289 */
1290 public RecycledViewPool getRecycledViewPool() {
1291 return mRecycler.getRecycledViewPool();
1292 }
1293
1294 /**
1295 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
1296 * This can be useful if you have multiple RecyclerViews with adapters that use the same
1297 * view types, for example if you have several data sets with the same kinds of item views
1298 * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
1299 *
1300 * @param pool Pool to set. If this parameter is null a new pool will be created and used.
1301 */
1302 public void setRecycledViewPool(RecycledViewPool pool) {
1303 mRecycler.setRecycledViewPool(pool);
1304 }
1305
1306 /**
1307 * Sets a new {@link ViewCacheExtension} to be used by the Recycler.
1308 *
1309 * @param extension ViewCacheExtension to be used or null if you want to clear the existing one.
1310 *
1311 * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)}
1312 */
1313 public void setViewCacheExtension(ViewCacheExtension extension) {
1314 mRecycler.setViewCacheExtension(extension);
1315 }
1316
1317 /**
1318 * Set the number of offscreen views to retain before adding them to the potentially shared
1319 * {@link #getRecycledViewPool() recycled view pool}.
1320 *
1321 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
1322 * a LayoutManager to reuse those views unmodified without needing to return to the adapter
1323 * to rebind them.</p>
1324 *
1325 * @param size Number of views to cache offscreen before returning them to the general
1326 * recycled view pool
1327 */
1328 public void setItemViewCacheSize(int size) {
1329 mRecycler.setViewCacheSize(size);
1330 }
1331
1332 /**
1333 * Return the current scrolling state of the RecyclerView.
1334 *
1335 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
1336 * {@link #SCROLL_STATE_SETTLING}
1337 */
1338 public int getScrollState() {
1339 return mScrollState;
1340 }
1341
1342 void setScrollState(int state) {
1343 if (state == mScrollState) {
1344 return;
1345 }
1346 if (DEBUG) {
1347 Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
1348 new Exception());
1349 }
1350 mScrollState = state;
1351 if (state != SCROLL_STATE_SETTLING) {
1352 stopScrollersInternal();
1353 }
1354 dispatchOnScrollStateChanged(state);
1355 }
1356
1357 /**
1358 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
1359 * affect both measurement and drawing of individual item views.
1360 *
1361 * <p>Item decorations are ordered. Decorations placed earlier in the list will
1362 * be run/queried/drawn first for their effects on item views. Padding added to views
1363 * will be nested; a padding added by an earlier decoration will mean further
1364 * item decorations in the list will be asked to draw/pad within the previous decoration's
1365 * given area.</p>
1366 *
1367 * @param decor Decoration to add
1368 * @param index Position in the decoration chain to insert this decoration at. If this value
1369 * is negative the decoration will be added at the end.
1370 */
1371 public void addItemDecoration(ItemDecoration decor, int index) {
1372 if (mLayout != null) {
1373 mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
1374 + " layout");
1375 }
1376 if (mItemDecorations.isEmpty()) {
1377 setWillNotDraw(false);
1378 }
1379 if (index < 0) {
1380 mItemDecorations.add(decor);
1381 } else {
1382 mItemDecorations.add(index, decor);
1383 }
1384 markItemDecorInsetsDirty();
1385 requestLayout();
1386 }
1387
1388 /**
1389 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
1390 * affect both measurement and drawing of individual item views.
1391 *
1392 * <p>Item decorations are ordered. Decorations placed earlier in the list will
1393 * be run/queried/drawn first for their effects on item views. Padding added to views
1394 * will be nested; a padding added by an earlier decoration will mean further
1395 * item decorations in the list will be asked to draw/pad within the previous decoration's
1396 * given area.</p>
1397 *
1398 * @param decor Decoration to add
1399 */
1400 public void addItemDecoration(ItemDecoration decor) {
1401 addItemDecoration(decor, -1);
1402 }
1403
1404 /**
1405 * Remove an {@link ItemDecoration} from this RecyclerView.
1406 *
1407 * <p>The given decoration will no longer impact the measurement and drawing of
1408 * item views.</p>
1409 *
1410 * @param decor Decoration to remove
1411 * @see #addItemDecoration(ItemDecoration)
1412 */
1413 public void removeItemDecoration(ItemDecoration decor) {
1414 if (mLayout != null) {
1415 mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or"
1416 + " layout");
1417 }
1418 mItemDecorations.remove(decor);
1419 if (mItemDecorations.isEmpty()) {
1420 setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
1421 }
1422 markItemDecorInsetsDirty();
1423 requestLayout();
1424 }
1425
1426 /**
1427 * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
1428 * <p>
1429 * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will
1430 * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be
1431 * true if childDrawingOrderCallback is not null, false otherwise.
1432 * <p>
1433 * Note that child drawing order may be overridden by View's elevation.
1434 *
1435 * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing
1436 * system.
1437 */
1438 public void setChildDrawingOrderCallback(ChildDrawingOrderCallback childDrawingOrderCallback) {
1439 if (childDrawingOrderCallback == mChildDrawingOrderCallback) {
1440 return;
1441 }
1442 mChildDrawingOrderCallback = childDrawingOrderCallback;
1443 setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null);
1444 }
1445
1446 /**
1447 * Set a listener that will be notified of any changes in scroll state or position.
1448 *
1449 * @param listener Listener to set or null to clear
1450 *
1451 * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and
1452 * {@link #removeOnScrollListener(OnScrollListener)}
1453 */
1454 @Deprecated
1455 public void setOnScrollListener(OnScrollListener listener) {
1456 mScrollListener = listener;
1457 }
1458
1459 /**
1460 * Add a listener that will be notified of any changes in scroll state or position.
1461 *
1462 * <p>Components that add a listener should take care to remove it when finished.
1463 * Other components that take ownership of a view may call {@link #clearOnScrollListeners()}
1464 * to remove all attached listeners.</p>
1465 *
1466 * @param listener listener to set or null to clear
1467 */
1468 public void addOnScrollListener(OnScrollListener listener) {
1469 if (mScrollListeners == null) {
1470 mScrollListeners = new ArrayList<>();
1471 }
1472 mScrollListeners.add(listener);
1473 }
1474
1475 /**
1476 * Remove a listener that was notified of any changes in scroll state or position.
1477 *
1478 * @param listener listener to set or null to clear
1479 */
1480 public void removeOnScrollListener(OnScrollListener listener) {
1481 if (mScrollListeners != null) {
1482 mScrollListeners.remove(listener);
1483 }
1484 }
1485
1486 /**
1487 * Remove all secondary listener that were notified of any changes in scroll state or position.
1488 */
1489 public void clearOnScrollListeners() {
1490 if (mScrollListeners != null) {
1491 mScrollListeners.clear();
1492 }
1493 }
1494
1495 /**
1496 * Convenience method to scroll to a certain position.
1497 *
1498 * RecyclerView does not implement scrolling logic, rather forwards the call to
1499 * {@link com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
1500 * @param position Scroll to this adapter position
1501 * @see com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)
1502 */
1503 public void scrollToPosition(int position) {
1504 if (mLayoutFrozen) {
1505 return;
1506 }
1507 stopScroll();
1508 if (mLayout == null) {
1509 Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
1510 + "Call setLayoutManager with a non-null argument.");
1511 return;
1512 }
1513 mLayout.scrollToPosition(position);
1514 awakenScrollBars();
1515 }
1516
1517 void jumpToPositionForSmoothScroller(int position) {
1518 if (mLayout == null) {
1519 return;
1520 }
1521 mLayout.scrollToPosition(position);
1522 awakenScrollBars();
1523 }
1524
1525 /**
1526 * Starts a smooth scroll to an adapter position.
1527 * <p>
1528 * To support smooth scrolling, you must override
1529 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
1530 * {@link SmoothScroller}.
1531 * <p>
1532 * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
1533 * provide a custom smooth scroll logic, override
1534 * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
1535 * LayoutManager.
1536 *
1537 * @param position The adapter position to scroll to
1538 * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
1539 */
1540 public void smoothScrollToPosition(int position) {
1541 if (mLayoutFrozen) {
1542 return;
1543 }
1544 if (mLayout == null) {
1545 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
1546 + "Call setLayoutManager with a non-null argument.");
1547 return;
1548 }
1549 mLayout.smoothScrollToPosition(this, mState, position);
1550 }
1551
1552 @Override
1553 public void scrollTo(int x, int y) {
1554 Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
1555 + "Use scrollToPosition instead");
1556 }
1557
1558 @Override
1559 public void scrollBy(int x, int y) {
1560 if (mLayout == null) {
1561 Log.e(TAG, "Cannot scroll without a LayoutManager set. "
1562 + "Call setLayoutManager with a non-null argument.");
1563 return;
1564 }
1565 if (mLayoutFrozen) {
1566 return;
1567 }
1568 final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
1569 final boolean canScrollVertical = mLayout.canScrollVertically();
1570 if (canScrollHorizontal || canScrollVertical) {
1571 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
1572 }
1573 }
1574
1575 /**
1576 * Helper method reflect data changes to the state.
1577 * <p>
1578 * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
1579 * but data actually changed.
1580 * <p>
1581 * This method consumes all deferred changes to avoid that case.
1582 */
1583 void consumePendingUpdateOperations() {
1584 if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
1585 Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
1586 dispatchLayout();
1587 Trace.endSection();
1588 return;
1589 }
1590 if (!mAdapterHelper.hasPendingUpdates()) {
1591 return;
1592 }
1593
1594 // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
1595 // of the visible items is affected and if not, just ignore the change.
1596 if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
1597 .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
1598 | AdapterHelper.UpdateOp.MOVE)) {
1599 Trace.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
1600 eatRequestLayout();
1601 onEnterLayoutOrScroll();
1602 mAdapterHelper.preProcess();
1603 if (!mLayoutRequestEaten) {
1604 if (hasUpdatedView()) {
1605 dispatchLayout();
1606 } else {
1607 // no need to layout, clean state
1608 mAdapterHelper.consumePostponedUpdates();
1609 }
1610 }
1611 resumeRequestLayout(true);
1612 onExitLayoutOrScroll();
1613 Trace.endSection();
1614 } else if (mAdapterHelper.hasPendingUpdates()) {
1615 Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
1616 dispatchLayout();
1617 Trace.endSection();
1618 }
1619 }
1620
1621 /**
1622 * @return True if an existing view holder needs to be updated
1623 */
1624 private boolean hasUpdatedView() {
1625 final int childCount = mChildHelper.getChildCount();
1626 for (int i = 0; i < childCount; i++) {
1627 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
1628 if (holder == null || holder.shouldIgnore()) {
1629 continue;
1630 }
1631 if (holder.isUpdated()) {
1632 return true;
1633 }
1634 }
1635 return false;
1636 }
1637
1638 /**
1639 * Does not perform bounds checking. Used by internal methods that have already validated input.
1640 * <p>
1641 * It also reports any unused scroll request to the related EdgeEffect.
1642 *
1643 * @param x The amount of horizontal scroll request
1644 * @param y The amount of vertical scroll request
1645 * @param ev The originating MotionEvent, or null if not from a touch event.
1646 *
1647 * @return Whether any scroll was consumed in either direction.
1648 */
1649 boolean scrollByInternal(int x, int y, MotionEvent ev) {
1650 int unconsumedX = 0, unconsumedY = 0;
1651 int consumedX = 0, consumedY = 0;
1652
1653 consumePendingUpdateOperations();
1654 if (mAdapter != null) {
1655 eatRequestLayout();
1656 onEnterLayoutOrScroll();
1657 Trace.beginSection(TRACE_SCROLL_TAG);
1658 if (x != 0) {
1659 consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
1660 unconsumedX = x - consumedX;
1661 }
1662 if (y != 0) {
1663 consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
1664 unconsumedY = y - consumedY;
1665 }
1666 Trace.endSection();
1667 repositionShadowingViews();
1668 onExitLayoutOrScroll();
1669 resumeRequestLayout(false);
1670 }
1671 if (!mItemDecorations.isEmpty()) {
1672 invalidate();
1673 }
1674
1675 if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
1676 // Update the last touch co-ords, taking any scroll offset into account
1677 mLastTouchX -= mScrollOffset[0];
1678 mLastTouchY -= mScrollOffset[1];
1679 if (ev != null) {
1680 ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
1681 }
1682 mNestedOffsets[0] += mScrollOffset[0];
1683 mNestedOffsets[1] += mScrollOffset[1];
1684 } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
1685 if (ev != null) {
1686 pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
1687 }
1688 considerReleasingGlowsOnScroll(x, y);
1689 }
1690 if (consumedX != 0 || consumedY != 0) {
1691 dispatchOnScrolled(consumedX, consumedY);
1692 }
1693 if (!awakenScrollBars()) {
1694 invalidate();
1695 }
1696 return consumedX != 0 || consumedY != 0;
1697 }
1698
1699 /**
1700 * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
1701 * range. This value is used to compute the length of the thumb within the scrollbar's track.
1702 * </p>
1703 *
1704 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1705 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
1706 *
1707 * <p>Default implementation returns 0.</p>
1708 *
1709 * <p>If you want to support scroll bars, override
1710 * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
1711 * LayoutManager. </p>
1712 *
1713 * @return The horizontal offset of the scrollbar's thumb
1714 * @see com.android.internal.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
1715 * (RecyclerView.State)
1716 */
1717 @Override
1718 public int computeHorizontalScrollOffset() {
1719 if (mLayout == null) {
1720 return 0;
1721 }
1722 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0;
1723 }
1724
1725 /**
1726 * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
1727 * horizontal range. This value is used to compute the length of the thumb within the
1728 * scrollbar's track.</p>
1729 *
1730 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1731 * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
1732 *
1733 * <p>Default implementation returns 0.</p>
1734 *
1735 * <p>If you want to support scroll bars, override
1736 * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
1737 * LayoutManager.</p>
1738 *
1739 * @return The horizontal extent of the scrollbar's thumb
1740 * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
1741 */
1742 @Override
1743 public int computeHorizontalScrollExtent() {
1744 if (mLayout == null) {
1745 return 0;
1746 }
1747 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
1748 }
1749
1750 /**
1751 * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
1752 *
1753 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1754 * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
1755 *
1756 * <p>Default implementation returns 0.</p>
1757 *
1758 * <p>If you want to support scroll bars, override
1759 * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
1760 * LayoutManager.</p>
1761 *
1762 * @return The total horizontal range represented by the vertical scrollbar
1763 * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
1764 */
1765 @Override
1766 public int computeHorizontalScrollRange() {
1767 if (mLayout == null) {
1768 return 0;
1769 }
1770 return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
1771 }
1772
1773 /**
1774 * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
1775 * This value is used to compute the length of the thumb within the scrollbar's track. </p>
1776 *
1777 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1778 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
1779 *
1780 * <p>Default implementation returns 0.</p>
1781 *
1782 * <p>If you want to support scroll bars, override
1783 * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
1784 * LayoutManager.</p>
1785 *
1786 * @return The vertical offset of the scrollbar's thumb
1787 * @see com.android.internal.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
1788 * (RecyclerView.State)
1789 */
1790 @Override
1791 public int computeVerticalScrollOffset() {
1792 if (mLayout == null) {
1793 return 0;
1794 }
1795 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
1796 }
1797
1798 /**
1799 * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
1800 * This value is used to compute the length of the thumb within the scrollbar's track.</p>
1801 *
1802 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1803 * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
1804 *
1805 * <p>Default implementation returns 0.</p>
1806 *
1807 * <p>If you want to support scroll bars, override
1808 * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
1809 * LayoutManager.</p>
1810 *
1811 * @return The vertical extent of the scrollbar's thumb
1812 * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
1813 */
1814 @Override
1815 public int computeVerticalScrollExtent() {
1816 if (mLayout == null) {
1817 return 0;
1818 }
1819 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
1820 }
1821
1822 /**
1823 * <p>Compute the vertical range that the vertical scrollbar represents.</p>
1824 *
1825 * <p>The range is expressed in arbitrary units that must be the same as the units used by
1826 * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
1827 *
1828 * <p>Default implementation returns 0.</p>
1829 *
1830 * <p>If you want to support scroll bars, override
1831 * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
1832 * LayoutManager.</p>
1833 *
1834 * @return The total vertical range represented by the vertical scrollbar
1835 * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
1836 */
1837 @Override
1838 public int computeVerticalScrollRange() {
1839 if (mLayout == null) {
1840 return 0;
1841 }
1842 return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
1843 }
1844
1845
1846 void eatRequestLayout() {
1847 mEatRequestLayout++;
1848 if (mEatRequestLayout == 1 && !mLayoutFrozen) {
1849 mLayoutRequestEaten = false;
1850 }
1851 }
1852
1853 void resumeRequestLayout(boolean performLayoutChildren) {
1854 if (mEatRequestLayout < 1) {
1855 //noinspection PointlessBooleanExpression
1856 if (DEBUG) {
1857 throw new IllegalStateException("invalid eat request layout count");
1858 }
1859 mEatRequestLayout = 1;
1860 }
1861 if (!performLayoutChildren) {
1862 // Reset the layout request eaten counter.
1863 // This is necessary since eatRequest calls can be nested in which case the other
1864 // call will override the inner one.
1865 // for instance:
1866 // eat layout for process adapter updates
1867 // eat layout for dispatchLayout
1868 // a bunch of req layout calls arrive
1869
1870 mLayoutRequestEaten = false;
1871 }
1872 if (mEatRequestLayout == 1) {
1873 // when layout is frozen we should delay dispatchLayout()
1874 if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen
1875 && mLayout != null && mAdapter != null) {
1876 dispatchLayout();
1877 }
1878 if (!mLayoutFrozen) {
1879 mLayoutRequestEaten = false;
1880 }
1881 }
1882 mEatRequestLayout--;
1883 }
1884
1885 /**
1886 * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called,
1887 * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called;
1888 * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)},
1889 * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
1890 * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
1891 * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
1892 * called.
1893 *
1894 * <p>
1895 * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link
1896 * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
1897 * RecyclerView, State, int)}.
1898 * <p>
1899 * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
1900 * stop frozen.
1901 * <p>
1902 * Note: Running ItemAnimator is not stopped automatically, it's caller's
1903 * responsibility to call ItemAnimator.end().
1904 *
1905 * @param frozen true to freeze layout and scroll, false to re-enable.
1906 */
1907 public void setLayoutFrozen(boolean frozen) {
1908 if (frozen != mLayoutFrozen) {
1909 assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
1910 if (!frozen) {
1911 mLayoutFrozen = false;
1912 if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
1913 requestLayout();
1914 }
1915 mLayoutRequestEaten = false;
1916 } else {
1917 final long now = SystemClock.uptimeMillis();
1918 MotionEvent cancelEvent = MotionEvent.obtain(now, now,
1919 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1920 onTouchEvent(cancelEvent);
1921 mLayoutFrozen = true;
1922 mIgnoreMotionEventTillDown = true;
1923 stopScroll();
1924 }
1925 }
1926 }
1927
1928 /**
1929 * Returns true if layout and scroll are frozen.
1930 *
1931 * @return true if layout and scroll are frozen
1932 * @see #setLayoutFrozen(boolean)
1933 */
1934 public boolean isLayoutFrozen() {
1935 return mLayoutFrozen;
1936 }
1937
1938 /**
1939 * Animate a scroll by the given amount of pixels along either axis.
1940 *
1941 * @param dx Pixels to scroll horizontally
1942 * @param dy Pixels to scroll vertically
1943 */
1944 public void smoothScrollBy(int dx, int dy) {
1945 smoothScrollBy(dx, dy, null);
1946 }
1947
1948 /**
1949 * Animate a scroll by the given amount of pixels along either axis.
1950 *
1951 * @param dx Pixels to scroll horizontally
1952 * @param dy Pixels to scroll vertically
1953 * @param interpolator {@link Interpolator} to be used for scrolling. If it is
1954 * {@code null}, RecyclerView is going to use the default interpolator.
1955 */
1956 public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
1957 if (mLayout == null) {
1958 Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
1959 + "Call setLayoutManager with a non-null argument.");
1960 return;
1961 }
1962 if (mLayoutFrozen) {
1963 return;
1964 }
1965 if (!mLayout.canScrollHorizontally()) {
1966 dx = 0;
1967 }
1968 if (!mLayout.canScrollVertically()) {
1969 dy = 0;
1970 }
1971 if (dx != 0 || dy != 0) {
1972 mViewFlinger.smoothScrollBy(dx, dy, interpolator);
1973 }
1974 }
1975
1976 /**
1977 * Begin a standard fling with an initial velocity along each axis in pixels per second.
1978 * If the velocity given is below the system-defined minimum this method will return false
1979 * and no fling will occur.
1980 *
1981 * @param velocityX Initial horizontal velocity in pixels per second
1982 * @param velocityY Initial vertical velocity in pixels per second
1983 * @return true if the fling was started, false if the velocity was too low to fling or
1984 * LayoutManager does not support scrolling in the axis fling is issued.
1985 *
1986 * @see LayoutManager#canScrollVertically()
1987 * @see LayoutManager#canScrollHorizontally()
1988 */
1989 public boolean fling(int velocityX, int velocityY) {
1990 if (mLayout == null) {
1991 Log.e(TAG, "Cannot fling without a LayoutManager set. "
1992 + "Call setLayoutManager with a non-null argument.");
1993 return false;
1994 }
1995 if (mLayoutFrozen) {
1996 return false;
1997 }
1998
1999 final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
2000 final boolean canScrollVertical = mLayout.canScrollVertically();
2001
2002 if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
2003 velocityX = 0;
2004 }
2005 if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
2006 velocityY = 0;
2007 }
2008 if (velocityX == 0 && velocityY == 0) {
2009 // If we don't have any velocity, return false
2010 return false;
2011 }
2012
2013 if (!dispatchNestedPreFling(velocityX, velocityY)) {
Zhen Zhanga7cb6972019-12-26 15:52:33 -08002014 final View firstChild = mLayout.getChildAt(0);
2015 final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
2016 boolean consumed = false;
2017 if (velocityY < 0) {
2018 consumed = getChildAdapterPosition(firstChild) > 0
2019 || firstChild.getTop() < getPaddingTop();
2020 }
2021
2022 if (velocityY > 0) {
2023 consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
2024 || lastChild.getBottom() > getHeight() - getPaddingBottom();
2025 }
2026
2027 dispatchNestedFling(velocityX, velocityY, consumed);
Aurimas Liutikas7149a632017-01-18 17:36:10 -08002028
2029 if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
2030 return true;
2031 }
2032
Zhen Zhanga7cb6972019-12-26 15:52:33 -08002033 final boolean canScroll = canScrollHorizontal || canScrollVertical;
2034
Aurimas Liutikas7149a632017-01-18 17:36:10 -08002035 if (canScroll) {
2036 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
2037 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
2038 mViewFlinger.fling(velocityX, velocityY);
2039 return true;
2040 }
2041 }
2042 return false;
2043 }
2044
2045 /**
2046 * Stop any current scroll in progress, such as one started by
2047 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
2048 */
2049 public void stopScroll() {
2050 setScrollState(SCROLL_STATE_IDLE);
2051 stopScrollersInternal();
2052 }
2053
2054 /**
2055 * Similar to {@link #stopScroll()} but does not set the state.
2056 */
2057 private void stopScrollersInternal() {
2058 mViewFlinger.stop();
2059 if (mLayout != null) {
2060 mLayout.stopSmoothScroller();
2061 }
2062 }
2063
2064 /**
2065 * Returns the minimum velocity to start a fling.
2066 *
2067 * @return The minimum velocity to start a fling
2068 */
2069 public int getMinFlingVelocity() {
2070 return mMinFlingVelocity;
2071 }
2072
2073
2074 /**
2075 * Returns the maximum fling velocity used by this RecyclerView.
2076 *
2077 * @return The maximum fling velocity used by this RecyclerView.
2078 */
2079 public int getMaxFlingVelocity() {
2080 return mMaxFlingVelocity;
2081 }
2082
2083 /**
2084 * Apply a pull to relevant overscroll glow effects
2085 */
2086 private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
2087 boolean invalidate = false;
2088 if (overscrollX < 0) {
2089 ensureLeftGlow();
2090 mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight());
2091 invalidate = true;
2092 } else if (overscrollX > 0) {
2093 ensureRightGlow();
2094 mRightGlow.onPull(overscrollX / getWidth(), y / getHeight());
2095 invalidate = true;
2096 }
2097
2098 if (overscrollY < 0) {
2099 ensureTopGlow();
2100 mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth());
2101 invalidate = true;
2102 } else if (overscrollY > 0) {
2103 ensureBottomGlow();
2104 mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth());
2105 invalidate = true;
2106 }
2107
2108 if (invalidate || overscrollX != 0 || overscrollY != 0) {
2109 postInvalidateOnAnimation();
2110 }
2111 }
2112
2113 private void releaseGlows() {
2114 boolean needsInvalidate = false;
2115 if (mLeftGlow != null) {
2116 mLeftGlow.onRelease();
2117 needsInvalidate = true;
2118 }
2119 if (mTopGlow != null) {
2120 mTopGlow.onRelease();
2121 needsInvalidate = true;
2122 }
2123 if (mRightGlow != null) {
2124 mRightGlow.onRelease();
2125 needsInvalidate = true;
2126 }
2127 if (mBottomGlow != null) {
2128 mBottomGlow.onRelease();
2129 needsInvalidate = true;
2130 }
2131 if (needsInvalidate) {
2132 postInvalidateOnAnimation();
2133 }
2134 }
2135
2136 void considerReleasingGlowsOnScroll(int dx, int dy) {
2137 boolean needsInvalidate = false;
2138 if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) {
2139 mLeftGlow.onRelease();
2140 needsInvalidate = true;
2141 }
2142 if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) {
2143 mRightGlow.onRelease();
2144 needsInvalidate = true;
2145 }
2146 if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) {
2147 mTopGlow.onRelease();
2148 needsInvalidate = true;
2149 }
2150 if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) {
2151 mBottomGlow.onRelease();
2152 needsInvalidate = true;
2153 }
2154 if (needsInvalidate) {
2155 postInvalidateOnAnimation();
2156 }
2157 }
2158
2159 void absorbGlows(int velocityX, int velocityY) {
2160 if (velocityX < 0) {
2161 ensureLeftGlow();
2162 mLeftGlow.onAbsorb(-velocityX);
2163 } else if (velocityX > 0) {
2164 ensureRightGlow();
2165 mRightGlow.onAbsorb(velocityX);
2166 }
2167
2168 if (velocityY < 0) {
2169 ensureTopGlow();
2170 mTopGlow.onAbsorb(-velocityY);
2171 } else if (velocityY > 0) {
2172 ensureBottomGlow();
2173 mBottomGlow.onAbsorb(velocityY);
2174 }
2175
2176 if (velocityX != 0 || velocityY != 0) {
2177 postInvalidateOnAnimation();
2178 }
2179 }
2180
2181 void ensureLeftGlow() {
2182 if (mLeftGlow != null) {
2183 return;
2184 }
2185 mLeftGlow = new EdgeEffect(getContext());
2186 if (mClipToPadding) {
2187 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
2188 getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
2189 } else {
2190 mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
2191 }
2192 }
2193
2194 void ensureRightGlow() {
2195 if (mRightGlow != null) {
2196 return;
2197 }
2198 mRightGlow = new EdgeEffect(getContext());
2199 if (mClipToPadding) {
2200 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
2201 getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
2202 } else {
2203 mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
2204 }
2205 }
2206
2207 void ensureTopGlow() {
2208 if (mTopGlow != null) {
2209 return;
2210 }
2211 mTopGlow = new EdgeEffect(getContext());
2212 if (mClipToPadding) {
2213 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
2214 getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
2215 } else {
2216 mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
2217 }
2218
2219 }
2220
2221 void ensureBottomGlow() {
2222 if (mBottomGlow != null) {
2223 return;
2224 }
2225 mBottomGlow = new EdgeEffect(getContext());
2226 if (mClipToPadding) {
2227 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
2228 getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
2229 } else {
2230 mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
2231 }
2232 }
2233
2234 void invalidateGlows() {
2235 mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
2236 }
2237
2238 /**
2239 * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
2240 * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
2241 * that differs from other ViewGroups.
2242 * <p>
2243 * It first does a focus search within the RecyclerView. If this search finds a View that is in
2244 * the focus direction with respect to the currently focused View, RecyclerView returns that
2245 * child as the next focus target. When it cannot find such child, it calls
2246 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
2247 * in the focus search direction. If LayoutManager adds a View that matches the
2248 * focus search criteria, it will be returned as the focus search result. Otherwise,
2249 * RecyclerView will call parent to handle the focus search like a regular ViewGroup.
2250 * <p>
2251 * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
2252 * is not in the focus direction is still valid focus target which may not be the desired
2253 * behavior if the Adapter has more children in the focus direction. To handle this case,
2254 * RecyclerView converts the focus direction to an absolute direction and makes a preliminary
2255 * focus search in that direction. If there are no Views to gain focus, it will call
2256 * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
2257 * focus search with the original (relative) direction. This allows RecyclerView to provide
2258 * better candidates to the focus search while still allowing the view system to take focus from
2259 * the RecyclerView and give it to a more suitable child if such child exists.
2260 *
2261 * @param focused The view that currently has focus
2262 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
2263 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
2264 * {@link View#FOCUS_BACKWARD} or 0 for not applicable.
2265 *
2266 * @return A new View that can be the next focus after the focused View
2267 */
2268 @Override
2269 public View focusSearch(View focused, int direction) {
2270 View result = mLayout.onInterceptFocusSearch(focused, direction);
2271 if (result != null) {
2272 return result;
2273 }
2274 final boolean canRunFocusFailure = mAdapter != null && mLayout != null
2275 && !isComputingLayout() && !mLayoutFrozen;
2276
2277 final FocusFinder ff = FocusFinder.getInstance();
2278 if (canRunFocusFailure
2279 && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
2280 // convert direction to absolute direction and see if we have a view there and if not
2281 // tell LayoutManager to add if it can.
2282 boolean needsFocusFailureLayout = false;
2283 if (mLayout.canScrollVertically()) {
2284 final int absDir =
2285 direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
2286 final View found = ff.findNextFocus(this, focused, absDir);
2287 needsFocusFailureLayout = found == null;
2288 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
2289 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
2290 direction = absDir;
2291 }
2292 }
2293 if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) {
2294 boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
2295 final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
2296 ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
2297 final View found = ff.findNextFocus(this, focused, absDir);
2298 needsFocusFailureLayout = found == null;
2299 if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
2300 // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
2301 direction = absDir;
2302 }
2303 }
2304 if (needsFocusFailureLayout) {
2305 consumePendingUpdateOperations();
2306 final View focusedItemView = findContainingItemView(focused);
2307 if (focusedItemView == null) {
2308 // panic, focused view is not a child anymore, cannot call super.
2309 return null;
2310 }
2311 eatRequestLayout();
2312 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
2313 resumeRequestLayout(false);
2314 }
2315 result = ff.findNextFocus(this, focused, direction);
2316 } else {
2317 result = ff.findNextFocus(this, focused, direction);
2318 if (result == null && canRunFocusFailure) {
2319 consumePendingUpdateOperations();
2320 final View focusedItemView = findContainingItemView(focused);
2321 if (focusedItemView == null) {
2322 // panic, focused view is not a child anymore, cannot call super.
2323 return null;
2324 }
2325 eatRequestLayout();
2326 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
2327 resumeRequestLayout(false);
2328 }
2329 }
2330 return isPreferredNextFocus(focused, result, direction)
2331 ? result : super.focusSearch(focused, direction);
2332 }
2333
2334 /**
2335 * Checks if the new focus candidate is a good enough candidate such that RecyclerView will
2336 * assign it as the next focus View instead of letting view hierarchy decide.
2337 * A good candidate means a View that is aligned in the focus direction wrt the focused View
2338 * and is not the RecyclerView itself.
2339 * When this method returns false, RecyclerView will let the parent make the decision so the
2340 * same View may still get the focus as a result of that search.
2341 */
2342 private boolean isPreferredNextFocus(View focused, View next, int direction) {
2343 if (next == null || next == this) {
2344 return false;
2345 }
2346 if (focused == null) {
2347 return true;
2348 }
2349
2350 if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
2351 final boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
2352 final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl
2353 ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
2354 if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) {
2355 return true;
2356 }
2357 if (direction == View.FOCUS_FORWARD) {
2358 return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN);
2359 } else {
2360 return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP);
2361 }
2362 } else {
2363 return isPreferredNextFocusAbsolute(focused, next, direction);
2364 }
2365
2366 }
2367
2368 /**
2369 * Logic taken from FocusSearch#isCandidate
2370 */
2371 private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) {
2372 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
2373 mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
2374 offsetDescendantRectToMyCoords(focused, mTempRect);
2375 offsetDescendantRectToMyCoords(next, mTempRect2);
2376 switch (direction) {
2377 case View.FOCUS_LEFT:
2378 return (mTempRect.right > mTempRect2.right
2379 || mTempRect.left >= mTempRect2.right)
2380 && mTempRect.left > mTempRect2.left;
2381 case View.FOCUS_RIGHT:
2382 return (mTempRect.left < mTempRect2.left
2383 || mTempRect.right <= mTempRect2.left)
2384 && mTempRect.right < mTempRect2.right;
2385 case View.FOCUS_UP:
2386 return (mTempRect.bottom > mTempRect2.bottom
2387 || mTempRect.top >= mTempRect2.bottom)
2388 && mTempRect.top > mTempRect2.top;
2389 case View.FOCUS_DOWN:
2390 return (mTempRect.top < mTempRect2.top
2391 || mTempRect.bottom <= mTempRect2.top)
2392 && mTempRect.bottom < mTempRect2.bottom;
2393 }
2394 throw new IllegalArgumentException("direction must be absolute. received:" + direction);
2395 }
2396
2397 @Override
2398 public void requestChildFocus(View child, View focused) {
2399 if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) {
2400 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
2401
2402 // get item decor offsets w/o refreshing. If they are invalid, there will be another
2403 // layout pass to fix them, then it is LayoutManager's responsibility to keep focused
2404 // View in viewport.
2405 final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams();
2406 if (focusedLayoutParams instanceof LayoutParams) {
2407 // if focused child has item decors, use them. Otherwise, ignore.
2408 final LayoutParams lp = (LayoutParams) focusedLayoutParams;
2409 if (!lp.mInsetsDirty) {
2410 final Rect insets = lp.mDecorInsets;
2411 mTempRect.left -= insets.left;
2412 mTempRect.right += insets.right;
2413 mTempRect.top -= insets.top;
2414 mTempRect.bottom += insets.bottom;
2415 }
2416 }
2417
2418 offsetDescendantRectToMyCoords(focused, mTempRect);
2419 offsetRectIntoDescendantCoords(child, mTempRect);
2420 requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
2421 }
2422 super.requestChildFocus(child, focused);
2423 }
2424
2425 @Override
2426 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
2427 return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
2428 }
2429
2430 @Override
2431 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2432 if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) {
2433 super.addFocusables(views, direction, focusableMode);
2434 }
2435 }
2436
2437 @Override
2438 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
2439 if (isComputingLayout()) {
2440 // if we are in the middle of a layout calculation, don't let any child take focus.
2441 // RV will handle it after layout calculation is finished.
2442 return false;
2443 }
2444 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
2445 }
2446
2447 @Override
2448 protected void onAttachedToWindow() {
2449 super.onAttachedToWindow();
2450 mLayoutOrScrollCounter = 0;
2451 mIsAttached = true;
2452 mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested();
2453 if (mLayout != null) {
2454 mLayout.dispatchAttachedToWindow(this);
2455 }
2456 mPostedAnimatorRunner = false;
2457
2458 if (ALLOW_THREAD_GAP_WORK) {
2459 // Register with gap worker
2460 mGapWorker = GapWorker.sGapWorker.get();
2461 if (mGapWorker == null) {
2462 mGapWorker = new GapWorker();
2463
2464 // break 60 fps assumption if data from display appears valid
2465 // NOTE: we only do this query once, statically, because it's very expensive (> 1ms)
2466 Display display = getDisplay();
2467 float refreshRate = 60.0f;
2468 if (!isInEditMode() && display != null) {
2469 float displayRefreshRate = display.getRefreshRate();
2470 if (displayRefreshRate >= 30.0f) {
2471 refreshRate = displayRefreshRate;
2472 }
2473 }
2474 mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
2475 GapWorker.sGapWorker.set(mGapWorker);
2476 }
2477 mGapWorker.add(this);
2478 }
2479 }
2480
2481 @Override
2482 protected void onDetachedFromWindow() {
2483 super.onDetachedFromWindow();
2484 if (mItemAnimator != null) {
2485 mItemAnimator.endAnimations();
2486 }
2487 stopScroll();
2488 mIsAttached = false;
2489 if (mLayout != null) {
2490 mLayout.dispatchDetachedFromWindow(this, mRecycler);
2491 }
2492 mPendingAccessibilityImportanceChange.clear();
2493 removeCallbacks(mItemAnimatorRunner);
2494 mViewInfoStore.onDetach();
2495
2496 if (ALLOW_THREAD_GAP_WORK) {
2497 // Unregister with gap worker
2498 mGapWorker.remove(this);
2499 mGapWorker = null;
2500 }
2501 }
2502
2503 /**
2504 * Returns true if RecyclerView is attached to window.
2505 */
2506 // @override
2507 public boolean isAttachedToWindow() {
2508 return mIsAttached;
2509 }
2510
2511 /**
2512 * Checks if RecyclerView is in the middle of a layout or scroll and throws an
2513 * {@link IllegalStateException} if it <b>is not</b>.
2514 *
2515 * @param message The message for the exception. Can be null.
2516 * @see #assertNotInLayoutOrScroll(String)
2517 */
2518 void assertInLayoutOrScroll(String message) {
2519 if (!isComputingLayout()) {
2520 if (message == null) {
2521 throw new IllegalStateException("Cannot call this method unless RecyclerView is "
2522 + "computing a layout or scrolling");
2523 }
2524 throw new IllegalStateException(message);
2525
2526 }
2527 }
2528
2529 /**
2530 * Checks if RecyclerView is in the middle of a layout or scroll and throws an
2531 * {@link IllegalStateException} if it <b>is</b>.
2532 *
2533 * @param message The message for the exception. Can be null.
2534 * @see #assertInLayoutOrScroll(String)
2535 */
2536 void assertNotInLayoutOrScroll(String message) {
2537 if (isComputingLayout()) {
2538 if (message == null) {
2539 throw new IllegalStateException("Cannot call this method while RecyclerView is "
2540 + "computing a layout or scrolling");
2541 }
2542 throw new IllegalStateException(message);
2543 }
2544 if (mDispatchScrollCounter > 0) {
2545 Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might be run"
2546 + " during a measure & layout pass where you cannot change the RecyclerView"
2547 + " data. Any method call that might change the structure of the RecyclerView"
2548 + " or the adapter contents should be postponed to the next frame.",
2549 new IllegalStateException(""));
2550 }
2551 }
2552
2553 /**
2554 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
2555 * to child views or this view's standard scrolling behavior.
2556 *
2557 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
2558 * returns true from
2559 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
2560 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
2561 * for each incoming MotionEvent until the end of the gesture.</p>
2562 *
2563 * @param listener Listener to add
2564 * @see SimpleOnItemTouchListener
2565 */
2566 public void addOnItemTouchListener(OnItemTouchListener listener) {
2567 mOnItemTouchListeners.add(listener);
2568 }
2569
2570 /**
2571 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
2572 *
2573 * @param listener Listener to remove
2574 */
2575 public void removeOnItemTouchListener(OnItemTouchListener listener) {
2576 mOnItemTouchListeners.remove(listener);
2577 if (mActiveOnItemTouchListener == listener) {
2578 mActiveOnItemTouchListener = null;
2579 }
2580 }
2581
2582 private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
2583 final int action = e.getAction();
2584 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
2585 mActiveOnItemTouchListener = null;
2586 }
2587
2588 final int listenerCount = mOnItemTouchListeners.size();
2589 for (int i = 0; i < listenerCount; i++) {
2590 final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
2591 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
2592 mActiveOnItemTouchListener = listener;
2593 return true;
2594 }
2595 }
2596 return false;
2597 }
2598
2599 private boolean dispatchOnItemTouch(MotionEvent e) {
2600 final int action = e.getAction();
2601 if (mActiveOnItemTouchListener != null) {
2602 if (action == MotionEvent.ACTION_DOWN) {
2603 // Stale state from a previous gesture, we're starting a new one. Clear it.
2604 mActiveOnItemTouchListener = null;
2605 } else {
2606 mActiveOnItemTouchListener.onTouchEvent(this, e);
2607 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
2608 // Clean up for the next gesture.
2609 mActiveOnItemTouchListener = null;
2610 }
2611 return true;
2612 }
2613 }
2614
2615 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
2616 // as called from onInterceptTouchEvent; skip it.
2617 if (action != MotionEvent.ACTION_DOWN) {
2618 final int listenerCount = mOnItemTouchListeners.size();
2619 for (int i = 0; i < listenerCount; i++) {
2620 final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
2621 if (listener.onInterceptTouchEvent(this, e)) {
2622 mActiveOnItemTouchListener = listener;
2623 return true;
2624 }
2625 }
2626 }
2627 return false;
2628 }
2629
2630 @Override
2631 public boolean onInterceptTouchEvent(MotionEvent e) {
2632 if (mLayoutFrozen) {
2633 // When layout is frozen, RV does not intercept the motion event.
2634 // A child view e.g. a button may still get the click.
2635 return false;
2636 }
2637 if (dispatchOnItemTouchIntercept(e)) {
2638 cancelTouch();
2639 return true;
2640 }
2641
2642 if (mLayout == null) {
2643 return false;
2644 }
2645
2646 final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
2647 final boolean canScrollVertically = mLayout.canScrollVertically();
2648
2649 if (mVelocityTracker == null) {
2650 mVelocityTracker = VelocityTracker.obtain();
2651 }
2652 mVelocityTracker.addMovement(e);
2653
2654 final int action = e.getActionMasked();
2655 final int actionIndex = e.getActionIndex();
2656
2657 switch (action) {
2658 case MotionEvent.ACTION_DOWN:
2659 if (mIgnoreMotionEventTillDown) {
2660 mIgnoreMotionEventTillDown = false;
2661 }
2662 mScrollPointerId = e.getPointerId(0);
2663 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
2664 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
2665
2666 if (mScrollState == SCROLL_STATE_SETTLING) {
2667 getParent().requestDisallowInterceptTouchEvent(true);
2668 setScrollState(SCROLL_STATE_DRAGGING);
2669 }
2670
2671 // Clear the nested offsets
2672 mNestedOffsets[0] = mNestedOffsets[1] = 0;
2673
2674 int nestedScrollAxis = View.SCROLL_AXIS_NONE;
2675 if (canScrollHorizontally) {
2676 nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
2677 }
2678 if (canScrollVertically) {
2679 nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
2680 }
2681 startNestedScroll(nestedScrollAxis);
2682 break;
2683
2684 case MotionEvent.ACTION_POINTER_DOWN:
2685 mScrollPointerId = e.getPointerId(actionIndex);
2686 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
2687 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
2688 break;
2689
2690 case MotionEvent.ACTION_MOVE: {
2691 final int index = e.findPointerIndex(mScrollPointerId);
2692 if (index < 0) {
2693 Log.e(TAG, "Error processing scroll; pointer index for id "
2694 + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
2695 return false;
2696 }
2697
2698 final int x = (int) (e.getX(index) + 0.5f);
2699 final int y = (int) (e.getY(index) + 0.5f);
2700 if (mScrollState != SCROLL_STATE_DRAGGING) {
2701 final int dx = x - mInitialTouchX;
2702 final int dy = y - mInitialTouchY;
2703 boolean startScroll = false;
2704 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
2705 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
2706 startScroll = true;
2707 }
2708 if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
2709 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
2710 startScroll = true;
2711 }
2712 if (startScroll) {
2713 setScrollState(SCROLL_STATE_DRAGGING);
2714 }
2715 }
2716 } break;
2717
2718 case MotionEvent.ACTION_POINTER_UP: {
2719 onPointerUp(e);
2720 } break;
2721
2722 case MotionEvent.ACTION_UP: {
2723 mVelocityTracker.clear();
2724 stopNestedScroll();
2725 } break;
2726
2727 case MotionEvent.ACTION_CANCEL: {
2728 cancelTouch();
2729 }
2730 }
2731 return mScrollState == SCROLL_STATE_DRAGGING;
2732 }
2733
2734 @Override
2735 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2736 final int listenerCount = mOnItemTouchListeners.size();
2737 for (int i = 0; i < listenerCount; i++) {
2738 final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
2739 listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
2740 }
2741 super.requestDisallowInterceptTouchEvent(disallowIntercept);
2742 }
2743
2744 @Override
2745 public boolean onTouchEvent(MotionEvent e) {
2746 if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
2747 return false;
2748 }
2749 if (dispatchOnItemTouch(e)) {
2750 cancelTouch();
2751 return true;
2752 }
2753
2754 if (mLayout == null) {
2755 return false;
2756 }
2757
2758 final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
2759 final boolean canScrollVertically = mLayout.canScrollVertically();
2760
2761 if (mVelocityTracker == null) {
2762 mVelocityTracker = VelocityTracker.obtain();
2763 }
2764 boolean eventAddedToVelocityTracker = false;
2765
2766 final MotionEvent vtev = MotionEvent.obtain(e);
2767 final int action = e.getActionMasked();
2768 final int actionIndex = e.getActionIndex();
2769
2770 if (action == MotionEvent.ACTION_DOWN) {
2771 mNestedOffsets[0] = mNestedOffsets[1] = 0;
2772 }
2773 vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
2774
2775 switch (action) {
2776 case MotionEvent.ACTION_DOWN: {
2777 mScrollPointerId = e.getPointerId(0);
2778 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
2779 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
2780
2781 int nestedScrollAxis = View.SCROLL_AXIS_NONE;
2782 if (canScrollHorizontally) {
2783 nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
2784 }
2785 if (canScrollVertically) {
2786 nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
2787 }
2788 startNestedScroll(nestedScrollAxis);
2789 } break;
2790
2791 case MotionEvent.ACTION_POINTER_DOWN: {
2792 mScrollPointerId = e.getPointerId(actionIndex);
2793 mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
2794 mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
2795 } break;
2796
2797 case MotionEvent.ACTION_MOVE: {
2798 final int index = e.findPointerIndex(mScrollPointerId);
2799 if (index < 0) {
2800 Log.e(TAG, "Error processing scroll; pointer index for id "
2801 + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
2802 return false;
2803 }
2804
2805 final int x = (int) (e.getX(index) + 0.5f);
2806 final int y = (int) (e.getY(index) + 0.5f);
2807 int dx = mLastTouchX - x;
2808 int dy = mLastTouchY - y;
2809
2810 if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
2811 dx -= mScrollConsumed[0];
2812 dy -= mScrollConsumed[1];
2813 vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
2814 // Updated the nested offsets
2815 mNestedOffsets[0] += mScrollOffset[0];
2816 mNestedOffsets[1] += mScrollOffset[1];
2817 }
2818
2819 if (mScrollState != SCROLL_STATE_DRAGGING) {
2820 boolean startScroll = false;
2821 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
2822 if (dx > 0) {
2823 dx -= mTouchSlop;
2824 } else {
2825 dx += mTouchSlop;
2826 }
2827 startScroll = true;
2828 }
2829 if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
2830 if (dy > 0) {
2831 dy -= mTouchSlop;
2832 } else {
2833 dy += mTouchSlop;
2834 }
2835 startScroll = true;
2836 }
2837 if (startScroll) {
2838 setScrollState(SCROLL_STATE_DRAGGING);
2839 }
2840 }
2841
2842 if (mScrollState == SCROLL_STATE_DRAGGING) {
2843 mLastTouchX = x - mScrollOffset[0];
2844 mLastTouchY = y - mScrollOffset[1];
2845
2846 if (scrollByInternal(
2847 canScrollHorizontally ? dx : 0,
2848 canScrollVertically ? dy : 0,
2849 vtev)) {
2850 getParent().requestDisallowInterceptTouchEvent(true);
2851 }
2852 if (mGapWorker != null && (dx != 0 || dy != 0)) {
2853 mGapWorker.postFromTraversal(this, dx, dy);
2854 }
2855 }
2856 } break;
2857
2858 case MotionEvent.ACTION_POINTER_UP: {
2859 onPointerUp(e);
2860 } break;
2861
2862 case MotionEvent.ACTION_UP: {
2863 mVelocityTracker.addMovement(vtev);
2864 eventAddedToVelocityTracker = true;
2865 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
2866 final float xvel = canScrollHorizontally
2867 ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
2868 final float yvel = canScrollVertically
2869 ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
2870 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
2871 setScrollState(SCROLL_STATE_IDLE);
2872 }
2873 resetTouch();
2874 } break;
2875
2876 case MotionEvent.ACTION_CANCEL: {
2877 cancelTouch();
2878 } break;
2879 }
2880
2881 if (!eventAddedToVelocityTracker) {
2882 mVelocityTracker.addMovement(vtev);
2883 }
2884 vtev.recycle();
2885
2886 return true;
2887 }
2888
2889 private void resetTouch() {
2890 if (mVelocityTracker != null) {
2891 mVelocityTracker.clear();
2892 }
2893 stopNestedScroll();
2894 releaseGlows();
2895 }
2896
2897 private void cancelTouch() {
2898 resetTouch();
2899 setScrollState(SCROLL_STATE_IDLE);
2900 }
2901
2902 private void onPointerUp(MotionEvent e) {
2903 final int actionIndex = e.getActionIndex();
2904 if (e.getPointerId(actionIndex) == mScrollPointerId) {
2905 // Pick a new pointer to pick up the slack.
2906 final int newIndex = actionIndex == 0 ? 1 : 0;
2907 mScrollPointerId = e.getPointerId(newIndex);
2908 mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);
2909 mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);
2910 }
2911 }
2912
2913 // @Override
2914 public boolean onGenericMotionEvent(MotionEvent event) {
2915 if (mLayout == null) {
2916 return false;
2917 }
2918 if (mLayoutFrozen) {
2919 return false;
2920 }
2921 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
2922 if (event.getAction() == MotionEvent.ACTION_SCROLL) {
2923 final float vScroll, hScroll;
2924 if (mLayout.canScrollVertically()) {
2925 // Inverse the sign of the vertical scroll to align the scroll orientation
2926 // with AbsListView.
2927 vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
2928 } else {
2929 vScroll = 0f;
2930 }
2931 if (mLayout.canScrollHorizontally()) {
2932 hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
2933 } else {
2934 hScroll = 0f;
2935 }
2936
2937 if (vScroll != 0 || hScroll != 0) {
2938 final float scrollFactor = getScrollFactor();
2939 scrollByInternal((int) (hScroll * scrollFactor),
2940 (int) (vScroll * scrollFactor), event);
2941 }
2942 }
2943 }
2944 return false;
2945 }
2946
2947 /**
2948 * Ported from View.getVerticalScrollFactor.
2949 */
2950 private float getScrollFactor() {
2951 if (mScrollFactor == Float.MIN_VALUE) {
2952 TypedValue outValue = new TypedValue();
2953 if (getContext().getTheme().resolveAttribute(
2954 android.R.attr.listPreferredItemHeight, outValue, true)) {
2955 mScrollFactor = outValue.getDimension(
2956 getContext().getResources().getDisplayMetrics());
2957 } else {
2958 return 0; //listPreferredItemHeight is not defined, no generic scrolling
2959 }
2960 }
2961 return mScrollFactor;
2962 }
2963
2964 @Override
2965 protected void onMeasure(int widthSpec, int heightSpec) {
2966 if (mLayout == null) {
2967 defaultOnMeasure(widthSpec, heightSpec);
2968 return;
2969 }
2970 if (mLayout.mAutoMeasure) {
2971 final int widthMode = MeasureSpec.getMode(widthSpec);
2972 final int heightMode = MeasureSpec.getMode(heightSpec);
2973 final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
2974 && heightMode == MeasureSpec.EXACTLY;
2975 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
2976 if (skipMeasure || mAdapter == null) {
2977 return;
2978 }
2979 if (mState.mLayoutStep == State.STEP_START) {
2980 dispatchLayoutStep1();
2981 }
2982 // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
2983 // consistency
2984 mLayout.setMeasureSpecs(widthSpec, heightSpec);
2985 mState.mIsMeasuring = true;
2986 dispatchLayoutStep2();
2987
2988 // now we can get the width and height from the children.
2989 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
2990
2991 // if RecyclerView has non-exact width and height and if there is at least one child
2992 // which also has non-exact width & height, we have to re-measure.
2993 if (mLayout.shouldMeasureTwice()) {
2994 mLayout.setMeasureSpecs(
2995 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
2996 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
2997 mState.mIsMeasuring = true;
2998 dispatchLayoutStep2();
2999 // now we can get the width and height from the children.
3000 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
3001 }
3002 } else {
3003 if (mHasFixedSize) {
3004 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
3005 return;
3006 }
3007 // custom onMeasure
3008 if (mAdapterUpdateDuringMeasure) {
3009 eatRequestLayout();
3010 onEnterLayoutOrScroll();
3011 processAdapterUpdatesAndSetAnimationFlags();
3012 onExitLayoutOrScroll();
3013
3014 if (mState.mRunPredictiveAnimations) {
3015 mState.mInPreLayout = true;
3016 } else {
3017 // consume remaining updates to provide a consistent state with the layout pass.
3018 mAdapterHelper.consumeUpdatesInOnePass();
3019 mState.mInPreLayout = false;
3020 }
3021 mAdapterUpdateDuringMeasure = false;
3022 resumeRequestLayout(false);
3023 }
3024
3025 if (mAdapter != null) {
3026 mState.mItemCount = mAdapter.getItemCount();
3027 } else {
3028 mState.mItemCount = 0;
3029 }
3030 eatRequestLayout();
3031 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
3032 resumeRequestLayout(false);
3033 mState.mInPreLayout = false; // clear
3034 }
3035 }
3036
3037 /**
3038 * Used when onMeasure is called before layout manager is set
3039 */
3040 void defaultOnMeasure(int widthSpec, int heightSpec) {
3041 // calling LayoutManager here is not pretty but that API is already public and it is better
3042 // than creating another method since this is internal.
3043 final int width = LayoutManager.chooseSize(widthSpec,
3044 getPaddingLeft() + getPaddingRight(),
3045 getMinimumWidth());
3046 final int height = LayoutManager.chooseSize(heightSpec,
3047 getPaddingTop() + getPaddingBottom(),
3048 getMinimumHeight());
3049
3050 setMeasuredDimension(width, height);
3051 }
3052
3053 @Override
3054 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
3055 super.onSizeChanged(w, h, oldw, oldh);
3056 if (w != oldw || h != oldh) {
3057 invalidateGlows();
3058 // layout's w/h are updated during measure/layout steps.
3059 }
3060 }
3061
3062 /**
3063 * Sets the {@link ItemAnimator} that will handle animations involving changes
3064 * to the items in this RecyclerView. By default, RecyclerView instantiates and
3065 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
3066 * enabled for the RecyclerView depends on the ItemAnimator and whether
3067 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
3068 * supports item animations}.
3069 *
3070 * @param animator The ItemAnimator being set. If null, no animations will occur
3071 * when changes occur to the items in this RecyclerView.
3072 */
3073 public void setItemAnimator(ItemAnimator animator) {
3074 if (mItemAnimator != null) {
3075 mItemAnimator.endAnimations();
3076 mItemAnimator.setListener(null);
3077 }
3078 mItemAnimator = animator;
3079 if (mItemAnimator != null) {
3080 mItemAnimator.setListener(mItemAnimatorListener);
3081 }
3082 }
3083
3084 void onEnterLayoutOrScroll() {
3085 mLayoutOrScrollCounter++;
3086 }
3087
3088 void onExitLayoutOrScroll() {
3089 mLayoutOrScrollCounter--;
3090 if (mLayoutOrScrollCounter < 1) {
3091 if (DEBUG && mLayoutOrScrollCounter < 0) {
3092 throw new IllegalStateException("layout or scroll counter cannot go below zero."
3093 + "Some calls are not matching");
3094 }
3095 mLayoutOrScrollCounter = 0;
3096 dispatchContentChangedIfNecessary();
3097 dispatchPendingImportantForAccessibilityChanges();
3098 }
3099 }
3100
3101 boolean isAccessibilityEnabled() {
3102 return mAccessibilityManager != null && mAccessibilityManager.isEnabled();
3103 }
3104
3105 private void dispatchContentChangedIfNecessary() {
3106 final int flags = mEatenAccessibilityChangeFlags;
3107 mEatenAccessibilityChangeFlags = 0;
3108 if (flags != 0 && isAccessibilityEnabled()) {
3109 final AccessibilityEvent event = AccessibilityEvent.obtain();
3110 event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3111 event.setContentChangeTypes(flags);
3112 sendAccessibilityEventUnchecked(event);
3113 }
3114 }
3115
3116 /**
3117 * Returns whether RecyclerView is currently computing a layout.
3118 * <p>
3119 * If this method returns true, it means that RecyclerView is in a lockdown state and any
3120 * attempt to update adapter contents will result in an exception because adapter contents
3121 * cannot be changed while RecyclerView is trying to compute the layout.
3122 * <p>
3123 * It is very unlikely that your code will be running during this state as it is
3124 * called by the framework when a layout traversal happens or RecyclerView starts to scroll
3125 * in response to system events (touch, accessibility etc).
3126 * <p>
3127 * This case may happen if you have some custom logic to change adapter contents in
3128 * response to a View callback (e.g. focus change callback) which might be triggered during a
3129 * layout calculation. In these cases, you should just postpone the change using a Handler or a
3130 * similar mechanism.
3131 *
3132 * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
3133 * otherwise
3134 */
3135 public boolean isComputingLayout() {
3136 return mLayoutOrScrollCounter > 0;
3137 }
3138
3139 /**
3140 * Returns true if an accessibility event should not be dispatched now. This happens when an
3141 * accessibility request arrives while RecyclerView does not have a stable state which is very
3142 * hard to handle for a LayoutManager. Instead, this method records necessary information about
3143 * the event and dispatches a window change event after the critical section is finished.
3144 *
3145 * @return True if the accessibility event should be postponed.
3146 */
3147 boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) {
3148 if (isComputingLayout()) {
3149 int type = 0;
3150 if (event != null) {
3151 type = event.getContentChangeTypes();
3152 }
3153 if (type == 0) {
3154 type = AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
3155 }
3156 mEatenAccessibilityChangeFlags |= type;
3157 return true;
3158 }
3159 return false;
3160 }
3161
3162 @Override
3163 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
3164 if (shouldDeferAccessibilityEvent(event)) {
3165 return;
3166 }
3167 super.sendAccessibilityEventUnchecked(event);
3168 }
3169
3170 /**
3171 * Gets the current ItemAnimator for this RecyclerView. A null return value
3172 * indicates that there is no animator and that item changes will happen without
3173 * any animations. By default, RecyclerView instantiates and
3174 * uses an instance of {@link DefaultItemAnimator}.
3175 *
3176 * @return ItemAnimator The current ItemAnimator. If null, no animations will occur
3177 * when changes occur to the items in this RecyclerView.
3178 */
3179 public ItemAnimator getItemAnimator() {
3180 return mItemAnimator;
3181 }
3182
3183 /**
3184 * Post a runnable to the next frame to run pending item animations. Only the first such
3185 * request will be posted, governed by the mPostedAnimatorRunner flag.
3186 */
3187 void postAnimationRunner() {
3188 if (!mPostedAnimatorRunner && mIsAttached) {
3189 postOnAnimation(mItemAnimatorRunner);
3190 mPostedAnimatorRunner = true;
3191 }
3192 }
3193
3194 private boolean predictiveItemAnimationsEnabled() {
3195 return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
3196 }
3197
3198 /**
3199 * Consumes adapter updates and calculates which type of animations we want to run.
3200 * Called in onMeasure and dispatchLayout.
3201 * <p>
3202 * This method may process only the pre-layout state of updates or all of them.
3203 */
3204 private void processAdapterUpdatesAndSetAnimationFlags() {
3205 if (mDataSetHasChangedAfterLayout) {
3206 // Processing these items have no value since data set changed unexpectedly.
3207 // Instead, we just reset it.
3208 mAdapterHelper.reset();
3209 mLayout.onItemsChanged(this);
3210 }
3211 // simple animations are a subset of advanced animations (which will cause a
3212 // pre-layout step)
3213 // If layout supports predictive animations, pre-process to decide if we want to run them
3214 if (predictiveItemAnimationsEnabled()) {
3215 mAdapterHelper.preProcess();
3216 } else {
3217 mAdapterHelper.consumeUpdatesInOnePass();
3218 }
3219 boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
3220 mState.mRunSimpleAnimations = mFirstLayoutComplete
3221 && mItemAnimator != null
3222 && (mDataSetHasChangedAfterLayout
3223 || animationTypeSupported
3224 || mLayout.mRequestedSimpleAnimations)
3225 && (!mDataSetHasChangedAfterLayout
3226 || mAdapter.hasStableIds());
3227 mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
3228 && animationTypeSupported
3229 && !mDataSetHasChangedAfterLayout
3230 && predictiveItemAnimationsEnabled();
3231 }
3232
3233 /**
3234 * Wrapper around layoutChildren() that handles animating changes caused by layout.
3235 * Animations work on the assumption that there are five different kinds of items
3236 * in play:
3237 * PERSISTENT: items are visible before and after layout
3238 * REMOVED: items were visible before layout and were removed by the app
3239 * ADDED: items did not exist before layout and were added by the app
3240 * DISAPPEARING: items exist in the data set before/after, but changed from
3241 * visible to non-visible in the process of layout (they were moved off
3242 * screen as a side-effect of other changes)
3243 * APPEARING: items exist in the data set before/after, but changed from
3244 * non-visible to visible in the process of layout (they were moved on
3245 * screen as a side-effect of other changes)
3246 * The overall approach figures out what items exist before/after layout and
3247 * infers one of the five above states for each of the items. Then the animations
3248 * are set up accordingly:
3249 * PERSISTENT views are animated via
3250 * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
3251 * DISAPPEARING views are animated via
3252 * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
3253 * APPEARING views are animated via
3254 * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
3255 * and changed views are animated via
3256 * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
3257 */
3258 void dispatchLayout() {
3259 if (mAdapter == null) {
3260 Log.e(TAG, "No adapter attached; skipping layout");
3261 // leave the state in START
3262 return;
3263 }
3264 if (mLayout == null) {
3265 Log.e(TAG, "No layout manager attached; skipping layout");
3266 // leave the state in START
3267 return;
3268 }
3269 mState.mIsMeasuring = false;
3270 if (mState.mLayoutStep == State.STEP_START) {
3271 dispatchLayoutStep1();
3272 mLayout.setExactMeasureSpecsFrom(this);
3273 dispatchLayoutStep2();
3274 } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
3275 || mLayout.getHeight() != getHeight()) {
3276 // First 2 steps are done in onMeasure but looks like we have to run again due to
3277 // changed size.
3278 mLayout.setExactMeasureSpecsFrom(this);
3279 dispatchLayoutStep2();
3280 } else {
3281 // always make sure we sync them (to ensure mode is exact)
3282 mLayout.setExactMeasureSpecsFrom(this);
3283 }
3284 dispatchLayoutStep3();
3285 }
3286
3287 private void saveFocusInfo() {
3288 View child = null;
3289 if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
3290 child = getFocusedChild();
3291 }
3292
3293 final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
3294 if (focusedVh == null) {
3295 resetFocusInfo();
3296 } else {
3297 mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
3298 // mFocusedItemPosition should hold the current adapter position of the previously
3299 // focused item. If the item is removed, we store the previous adapter position of the
3300 // removed item.
3301 mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION
3302 : (focusedVh.isRemoved() ? focusedVh.mOldPosition
3303 : focusedVh.getAdapterPosition());
3304 mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
3305 }
3306 }
3307
3308 private void resetFocusInfo() {
3309 mState.mFocusedItemId = NO_ID;
3310 mState.mFocusedItemPosition = NO_POSITION;
3311 mState.mFocusedSubChildId = View.NO_ID;
3312 }
3313
3314 /**
3315 * Finds the best view candidate to request focus on using mFocusedItemPosition index of the
3316 * previously focused item. It first traverses the adapter forward to find a focusable candidate
3317 * and if no such candidate is found, it reverses the focus search direction for the items
3318 * before the mFocusedItemPosition'th index;
3319 * @return The best candidate to request focus on, or null if no such candidate exists. Null
3320 * indicates all the existing adapter items are unfocusable.
3321 */
3322 @Nullable
3323 private View findNextViewToFocus() {
3324 int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition
3325 : 0;
3326 ViewHolder nextFocus;
3327 final int itemCount = mState.getItemCount();
3328 for (int i = startFocusSearchIndex; i < itemCount; i++) {
3329 nextFocus = findViewHolderForAdapterPosition(i);
3330 if (nextFocus == null) {
3331 break;
3332 }
3333 if (nextFocus.itemView.hasFocusable()) {
3334 return nextFocus.itemView;
3335 }
3336 }
3337 final int limit = Math.min(itemCount, startFocusSearchIndex);
3338 for (int i = limit - 1; i >= 0; i--) {
3339 nextFocus = findViewHolderForAdapterPosition(i);
3340 if (nextFocus == null) {
3341 return null;
3342 }
3343 if (nextFocus.itemView.hasFocusable()) {
3344 return nextFocus.itemView;
3345 }
3346 }
3347 return null;
3348 }
3349
3350 private void recoverFocusFromState() {
3351 if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus()
3352 || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS
3353 || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) {
3354 // No-op if either of these cases happens:
3355 // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus
3356 // before its children and is focused (i.e. it already stole the focus away from its
3357 // descendants).
3358 return;
3359 }
3360 // only recover focus if RV itself has the focus or the focused view is hidden
3361 if (!isFocused()) {
3362 final View focusedChild = getFocusedChild();
3363 if (IGNORE_DETACHED_FOCUSED_CHILD
3364 && (focusedChild.getParent() == null || !focusedChild.hasFocus())) {
3365 // Special handling of API 15-. A focused child can be invalid because mFocus is not
3366 // cleared when the child is detached (mParent = null),
3367 // This happens because clearFocus on API 15- does not invalidate mFocus of its
3368 // parent when this child is detached.
3369 // For API 16+, this is not an issue because requestFocus takes care of clearing the
3370 // prior detached focused child. For API 15- the problem happens in 2 cases because
3371 // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called
3372 // for the current focused item which calls clearChild or 2. when the prior focused
3373 // child is removed, removeDetachedView called in layout step 3 which calls
3374 // clearChild. We should ignore this invalid focused child in all our calculations
3375 // for the next view to receive focus, and apply the focus recovery logic instead.
3376 if (mChildHelper.getChildCount() == 0) {
3377 // No children left. Request focus on the RV itself since one of its children
3378 // was holding focus previously.
3379 requestFocus();
3380 return;
3381 }
3382 } else if (!mChildHelper.isHidden(focusedChild)) {
3383 // If the currently focused child is hidden, apply the focus recovery logic.
3384 // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/.
3385 return;
3386 }
3387 }
3388 ViewHolder focusTarget = null;
3389 // RV first attempts to locate the previously focused item to request focus on using
3390 // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to
3391 // find the next best candidate to request focus on based on mFocusedItemPosition.
3392 if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) {
3393 focusTarget = findViewHolderForItemId(mState.mFocusedItemId);
3394 }
3395 View viewToFocus = null;
3396 if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView)
3397 || !focusTarget.itemView.hasFocusable()) {
3398 if (mChildHelper.getChildCount() > 0) {
3399 // At this point, RV has focus and either of these conditions are true:
3400 // 1. There's no previously focused item either because RV received focused before
3401 // layout, or the previously focused item was removed, or RV doesn't have stable IDs
3402 // 2. Previous focus child is hidden, or 3. Previous focused child is no longer
3403 // focusable. In either of these cases, we make sure that RV still passes down the
3404 // focus to one of its focusable children using a best-effort algorithm.
3405 viewToFocus = findNextViewToFocus();
3406 }
3407 } else {
3408 // looks like the focused item has been replaced with another view that represents the
3409 // same item in the adapter. Request focus on that.
3410 viewToFocus = focusTarget.itemView;
3411 }
3412
3413 if (viewToFocus != null) {
3414 if (mState.mFocusedSubChildId != NO_ID) {
3415 View child = viewToFocus.findViewById(mState.mFocusedSubChildId);
3416 if (child != null && child.isFocusable()) {
3417 viewToFocus = child;
3418 }
3419 }
3420 viewToFocus.requestFocus();
3421 }
3422 }
3423
3424 private int getDeepestFocusedViewWithId(View view) {
3425 int lastKnownId = view.getId();
3426 while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
3427 view = ((ViewGroup) view).getFocusedChild();
3428 final int id = view.getId();
3429 if (id != View.NO_ID) {
3430 lastKnownId = view.getId();
3431 }
3432 }
3433 return lastKnownId;
3434 }
3435
3436 /**
3437 * The first step of a layout where we;
3438 * - process adapter updates
3439 * - decide which animation should run
3440 * - save information about current views
3441 * - If necessary, run predictive layout and save its information
3442 */
3443 private void dispatchLayoutStep1() {
3444 mState.assertLayoutStep(State.STEP_START);
3445 mState.mIsMeasuring = false;
3446 eatRequestLayout();
3447 mViewInfoStore.clear();
3448 onEnterLayoutOrScroll();
3449 processAdapterUpdatesAndSetAnimationFlags();
3450 saveFocusInfo();
3451 mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
3452 mItemsAddedOrRemoved = mItemsChanged = false;
3453 mState.mInPreLayout = mState.mRunPredictiveAnimations;
3454 mState.mItemCount = mAdapter.getItemCount();
3455 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
3456
3457 if (mState.mRunSimpleAnimations) {
3458 // Step 0: Find out where all non-removed items are, pre-layout
3459 int count = mChildHelper.getChildCount();
3460 for (int i = 0; i < count; ++i) {
3461 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
3462 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
3463 continue;
3464 }
3465 final ItemHolderInfo animationInfo = mItemAnimator
3466 .recordPreLayoutInformation(mState, holder,
3467 ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
3468 holder.getUnmodifiedPayloads());
3469 mViewInfoStore.addToPreLayout(holder, animationInfo);
3470 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
3471 && !holder.shouldIgnore() && !holder.isInvalid()) {
3472 long key = getChangedHolderKey(holder);
3473 // This is NOT the only place where a ViewHolder is added to old change holders
3474 // list. There is another case where:
3475 // * A VH is currently hidden but not deleted
3476 // * The hidden item is changed in the adapter
3477 // * Layout manager decides to layout the item in the pre-Layout pass (step1)
3478 // When this case is detected, RV will un-hide that view and add to the old
3479 // change holders list.
3480 mViewInfoStore.addToOldChangeHolders(key, holder);
3481 }
3482 }
3483 }
3484 if (mState.mRunPredictiveAnimations) {
3485 // Step 1: run prelayout: This will use the old positions of items. The layout manager
3486 // is expected to layout everything, even removed items (though not to add removed
3487 // items back to the container). This gives the pre-layout position of APPEARING views
3488 // which come into existence as part of the real layout.
3489
3490 // Save old positions so that LayoutManager can run its mapping logic.
3491 saveOldPositions();
3492 final boolean didStructureChange = mState.mStructureChanged;
3493 mState.mStructureChanged = false;
3494 // temporarily disable flag because we are asking for previous layout
3495 mLayout.onLayoutChildren(mRecycler, mState);
3496 mState.mStructureChanged = didStructureChange;
3497
3498 for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
3499 final View child = mChildHelper.getChildAt(i);
3500 final ViewHolder viewHolder = getChildViewHolderInt(child);
3501 if (viewHolder.shouldIgnore()) {
3502 continue;
3503 }
3504 if (!mViewInfoStore.isInPreLayout(viewHolder)) {
3505 int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
3506 boolean wasHidden = viewHolder
3507 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
3508 if (!wasHidden) {
3509 flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
3510 }
3511 final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
3512 mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
3513 if (wasHidden) {
3514 recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
3515 } else {
3516 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
3517 }
3518 }
3519 }
3520 // we don't process disappearing list because they may re-appear in post layout pass.
3521 clearOldPositions();
3522 } else {
3523 clearOldPositions();
3524 }
3525 onExitLayoutOrScroll();
3526 resumeRequestLayout(false);
3527 mState.mLayoutStep = State.STEP_LAYOUT;
3528 }
3529
3530 /**
3531 * The second layout step where we do the actual layout of the views for the final state.
3532 * This step might be run multiple times if necessary (e.g. measure).
3533 */
3534 private void dispatchLayoutStep2() {
3535 eatRequestLayout();
3536 onEnterLayoutOrScroll();
3537 mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
3538 mAdapterHelper.consumeUpdatesInOnePass();
3539 mState.mItemCount = mAdapter.getItemCount();
3540 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
3541
3542 // Step 2: Run layout
3543 mState.mInPreLayout = false;
3544 mLayout.onLayoutChildren(mRecycler, mState);
3545
3546 mState.mStructureChanged = false;
3547 mPendingSavedState = null;
3548
3549 // onLayoutChildren may have caused client code to disable item animations; re-check
3550 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
3551 mState.mLayoutStep = State.STEP_ANIMATIONS;
3552 onExitLayoutOrScroll();
3553 resumeRequestLayout(false);
3554 }
3555
3556 /**
3557 * The final step of the layout where we save the information about views for animations,
3558 * trigger animations and do any necessary cleanup.
3559 */
3560 private void dispatchLayoutStep3() {
3561 mState.assertLayoutStep(State.STEP_ANIMATIONS);
3562 eatRequestLayout();
3563 onEnterLayoutOrScroll();
3564 mState.mLayoutStep = State.STEP_START;
3565 if (mState.mRunSimpleAnimations) {
3566 // Step 3: Find out where things are now, and process change animations.
3567 // traverse list in reverse because we may call animateChange in the loop which may
3568 // remove the target view holder.
3569 for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
3570 ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
3571 if (holder.shouldIgnore()) {
3572 continue;
3573 }
3574 long key = getChangedHolderKey(holder);
3575 final ItemHolderInfo animationInfo = mItemAnimator
3576 .recordPostLayoutInformation(mState, holder);
3577 ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
3578 if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
3579 // run a change animation
3580
3581 // If an Item is CHANGED but the updated version is disappearing, it creates
3582 // a conflicting case.
3583 // Since a view that is marked as disappearing is likely to be going out of
3584 // bounds, we run a change animation. Both views will be cleaned automatically
3585 // once their animations finish.
3586 // On the other hand, if it is the same view holder instance, we run a
3587 // disappearing animation instead because we are not going to rebind the updated
3588 // VH unless it is enforced by the layout manager.
3589 final boolean oldDisappearing = mViewInfoStore.isDisappearing(
3590 oldChangeViewHolder);
3591 final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
3592 if (oldDisappearing && oldChangeViewHolder == holder) {
3593 // run disappear animation instead of change
3594 mViewInfoStore.addToPostLayout(holder, animationInfo);
3595 } else {
3596 final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
3597 oldChangeViewHolder);
3598 // we add and remove so that any post info is merged.
3599 mViewInfoStore.addToPostLayout(holder, animationInfo);
3600 ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
3601 if (preInfo == null) {
3602 handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
3603 } else {
3604 animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
3605 oldDisappearing, newDisappearing);
3606 }
3607 }
3608 } else {
3609 mViewInfoStore.addToPostLayout(holder, animationInfo);
3610 }
3611 }
3612
3613 // Step 4: Process view info lists and trigger animations
3614 mViewInfoStore.process(mViewInfoProcessCallback);
3615 }
3616
3617 mLayout.removeAndRecycleScrapInt(mRecycler);
3618 mState.mPreviousLayoutItemCount = mState.mItemCount;
3619 mDataSetHasChangedAfterLayout = false;
3620 mState.mRunSimpleAnimations = false;
3621
3622 mState.mRunPredictiveAnimations = false;
3623 mLayout.mRequestedSimpleAnimations = false;
3624 if (mRecycler.mChangedScrap != null) {
3625 mRecycler.mChangedScrap.clear();
3626 }
3627 if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
3628 // Initial prefetch has expanded cache, so reset until next prefetch.
3629 // This prevents initial prefetches from expanding the cache permanently.
3630 mLayout.mPrefetchMaxCountObserved = 0;
3631 mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
3632 mRecycler.updateViewCacheSize();
3633 }
3634
3635 mLayout.onLayoutCompleted(mState);
3636 onExitLayoutOrScroll();
3637 resumeRequestLayout(false);
3638 mViewInfoStore.clear();
3639 if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
3640 dispatchOnScrolled(0, 0);
3641 }
3642 recoverFocusFromState();
3643 resetFocusInfo();
3644 }
3645
3646 /**
3647 * This handles the case where there is an unexpected VH missing in the pre-layout map.
3648 * <p>
3649 * We might be able to detect the error in the application which will help the developer to
3650 * resolve the issue.
3651 * <p>
3652 * If it is not an expected error, we at least print an error to notify the developer and ignore
3653 * the animation.
3654 *
3655 * https://code.google.com/p/android/issues/detail?id=193958
3656 *
3657 * @param key The change key
3658 * @param holder Current ViewHolder
3659 * @param oldChangeViewHolder Changed ViewHolder
3660 */
3661 private void handleMissingPreInfoForChangeError(long key,
3662 ViewHolder holder, ViewHolder oldChangeViewHolder) {
3663 // check if two VH have the same key, if so, print that as an error
3664 final int childCount = mChildHelper.getChildCount();
3665 for (int i = 0; i < childCount; i++) {
3666 View view = mChildHelper.getChildAt(i);
3667 ViewHolder other = getChildViewHolderInt(view);
3668 if (other == holder) {
3669 continue;
3670 }
3671 final long otherKey = getChangedHolderKey(other);
3672 if (otherKey == key) {
3673 if (mAdapter != null && mAdapter.hasStableIds()) {
3674 throw new IllegalStateException("Two different ViewHolders have the same stable"
3675 + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT"
3676 + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
3677 } else {
3678 throw new IllegalStateException("Two different ViewHolders have the same change"
3679 + " ID. This might happen due to inconsistent Adapter update events or"
3680 + " if the LayoutManager lays out the same View multiple times."
3681 + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
3682 }
3683 }
3684 }
3685 // Very unlikely to happen but if it does, notify the developer.
3686 Log.e(TAG, "Problem while matching changed view holders with the new"
3687 + "ones. The pre-layout information for the change holder " + oldChangeViewHolder
3688 + " cannot be found but it is necessary for " + holder);
3689 }
3690
3691 /**
3692 * Records the animation information for a view holder that was bounced from hidden list. It
3693 * also clears the bounce back flag.
3694 */
3695 void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder,
3696 ItemHolderInfo animationInfo) {
3697 // looks like this view bounced back from hidden list!
3698 viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
3699 if (mState.mTrackOldChangeHolders && viewHolder.isUpdated()
3700 && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) {
3701 long key = getChangedHolderKey(viewHolder);
3702 mViewInfoStore.addToOldChangeHolders(key, viewHolder);
3703 }
3704 mViewInfoStore.addToPreLayout(viewHolder, animationInfo);
3705 }
3706
3707 private void findMinMaxChildLayoutPositions(int[] into) {
3708 final int count = mChildHelper.getChildCount();
3709 if (count == 0) {
3710 into[0] = NO_POSITION;
3711 into[1] = NO_POSITION;
3712 return;
3713 }
3714 int minPositionPreLayout = Integer.MAX_VALUE;
3715 int maxPositionPreLayout = Integer.MIN_VALUE;
3716 for (int i = 0; i < count; ++i) {
3717 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
3718 if (holder.shouldIgnore()) {
3719 continue;
3720 }
3721 final int pos = holder.getLayoutPosition();
3722 if (pos < minPositionPreLayout) {
3723 minPositionPreLayout = pos;
3724 }
3725 if (pos > maxPositionPreLayout) {
3726 maxPositionPreLayout = pos;
3727 }
3728 }
3729 into[0] = minPositionPreLayout;
3730 into[1] = maxPositionPreLayout;
3731 }
3732
3733 private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
3734 findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
3735 return mMinMaxLayoutPositions[0] != minPositionPreLayout
3736 || mMinMaxLayoutPositions[1] != maxPositionPreLayout;
3737 }
3738
3739 @Override
3740 protected void removeDetachedView(View child, boolean animate) {
3741 ViewHolder vh = getChildViewHolderInt(child);
3742 if (vh != null) {
3743 if (vh.isTmpDetached()) {
3744 vh.clearTmpDetachFlag();
3745 } else if (!vh.shouldIgnore()) {
3746 throw new IllegalArgumentException("Called removeDetachedView with a view which"
3747 + " is not flagged as tmp detached." + vh);
3748 }
3749 }
3750 dispatchChildDetached(child);
3751 super.removeDetachedView(child, animate);
3752 }
3753
3754 /**
3755 * Returns a unique key to be used while handling change animations.
3756 * It might be child's position or stable id depending on the adapter type.
3757 */
3758 long getChangedHolderKey(ViewHolder holder) {
3759 return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
3760 }
3761
3762 void animateAppearance(@NonNull ViewHolder itemHolder,
3763 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
3764 itemHolder.setIsRecyclable(false);
3765 if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
3766 postAnimationRunner();
3767 }
3768 }
3769
3770 void animateDisappearance(@NonNull ViewHolder holder,
3771 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
3772 addAnimatingView(holder);
3773 holder.setIsRecyclable(false);
3774 if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
3775 postAnimationRunner();
3776 }
3777 }
3778
3779 private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
3780 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
3781 boolean oldHolderDisappearing, boolean newHolderDisappearing) {
3782 oldHolder.setIsRecyclable(false);
3783 if (oldHolderDisappearing) {
3784 addAnimatingView(oldHolder);
3785 }
3786 if (oldHolder != newHolder) {
3787 if (newHolderDisappearing) {
3788 addAnimatingView(newHolder);
3789 }
3790 oldHolder.mShadowedHolder = newHolder;
3791 // old holder should disappear after animation ends
3792 addAnimatingView(oldHolder);
3793 mRecycler.unscrapView(oldHolder);
3794 newHolder.setIsRecyclable(false);
3795 newHolder.mShadowingHolder = oldHolder;
3796 }
3797 if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
3798 postAnimationRunner();
3799 }
3800 }
3801
3802 @Override
3803 protected void onLayout(boolean changed, int l, int t, int r, int b) {
3804 Trace.beginSection(TRACE_ON_LAYOUT_TAG);
3805 dispatchLayout();
3806 Trace.endSection();
3807 mFirstLayoutComplete = true;
3808 }
3809
3810 @Override
3811 public void requestLayout() {
3812 if (mEatRequestLayout == 0 && !mLayoutFrozen) {
3813 super.requestLayout();
3814 } else {
3815 mLayoutRequestEaten = true;
3816 }
3817 }
3818
3819 void markItemDecorInsetsDirty() {
3820 final int childCount = mChildHelper.getUnfilteredChildCount();
3821 for (int i = 0; i < childCount; i++) {
3822 final View child = mChildHelper.getUnfilteredChildAt(i);
3823 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
3824 }
3825 mRecycler.markItemDecorInsetsDirty();
3826 }
3827
3828 @Override
3829 public void draw(Canvas c) {
3830 super.draw(c);
3831
3832 final int count = mItemDecorations.size();
3833 for (int i = 0; i < count; i++) {
3834 mItemDecorations.get(i).onDrawOver(c, this, mState);
3835 }
3836 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
3837 // need find children closest to edges. Not sure if it is worth the effort.
3838 boolean needsInvalidate = false;
3839 if (mLeftGlow != null && !mLeftGlow.isFinished()) {
3840 final int restore = c.save();
3841 final int padding = mClipToPadding ? getPaddingBottom() : 0;
3842 c.rotate(270);
3843 c.translate(-getHeight() + padding, 0);
3844 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
3845 c.restoreToCount(restore);
3846 }
3847 if (mTopGlow != null && !mTopGlow.isFinished()) {
3848 final int restore = c.save();
3849 if (mClipToPadding) {
3850 c.translate(getPaddingLeft(), getPaddingTop());
3851 }
3852 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
3853 c.restoreToCount(restore);
3854 }
3855 if (mRightGlow != null && !mRightGlow.isFinished()) {
3856 final int restore = c.save();
3857 final int width = getWidth();
3858 final int padding = mClipToPadding ? getPaddingTop() : 0;
3859 c.rotate(90);
3860 c.translate(-padding, -width);
3861 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
3862 c.restoreToCount(restore);
3863 }
3864 if (mBottomGlow != null && !mBottomGlow.isFinished()) {
3865 final int restore = c.save();
3866 c.rotate(180);
3867 if (mClipToPadding) {
3868 c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
3869 } else {
3870 c.translate(-getWidth(), -getHeight());
3871 }
3872 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
3873 c.restoreToCount(restore);
3874 }
3875
3876 // If some views are animating, ItemDecorators are likely to move/change with them.
3877 // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
3878 // display lists are not invalidated.
3879 if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
3880 && mItemAnimator.isRunning()) {
3881 needsInvalidate = true;
3882 }
3883
3884 if (needsInvalidate) {
3885 postInvalidateOnAnimation();
3886 }
3887 }
3888
3889 @Override
3890 public void onDraw(Canvas c) {
3891 super.onDraw(c);
3892
3893 final int count = mItemDecorations.size();
3894 for (int i = 0; i < count; i++) {
3895 mItemDecorations.get(i).onDraw(c, this, mState);
3896 }
3897 }
3898
3899 @Override
3900 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3901 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
3902 }
3903
3904 @Override
3905 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
3906 if (mLayout == null) {
3907 throw new IllegalStateException("RecyclerView has no LayoutManager");
3908 }
3909 return mLayout.generateDefaultLayoutParams();
3910 }
3911
3912 @Override
3913 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3914 if (mLayout == null) {
3915 throw new IllegalStateException("RecyclerView has no LayoutManager");
3916 }
3917 return mLayout.generateLayoutParams(getContext(), attrs);
3918 }
3919
3920 @Override
3921 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3922 if (mLayout == null) {
3923 throw new IllegalStateException("RecyclerView has no LayoutManager");
3924 }
3925 return mLayout.generateLayoutParams(p);
3926 }
3927
3928 /**
3929 * Returns true if RecyclerView is currently running some animations.
3930 * <p>
3931 * If you want to be notified when animations are finished, use
3932 * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}.
3933 *
3934 * @return True if there are some item animations currently running or waiting to be started.
3935 */
3936 public boolean isAnimating() {
3937 return mItemAnimator != null && mItemAnimator.isRunning();
3938 }
3939
3940 void saveOldPositions() {
3941 final int childCount = mChildHelper.getUnfilteredChildCount();
3942 for (int i = 0; i < childCount; i++) {
3943 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
3944 if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) {
3945 throw new IllegalStateException("view holder cannot have position -1 unless it"
3946 + " is removed");
3947 }
3948 if (!holder.shouldIgnore()) {
3949 holder.saveOldPosition();
3950 }
3951 }
3952 }
3953
3954 void clearOldPositions() {
3955 final int childCount = mChildHelper.getUnfilteredChildCount();
3956 for (int i = 0; i < childCount; i++) {
3957 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
3958 if (!holder.shouldIgnore()) {
3959 holder.clearOldPosition();
3960 }
3961 }
3962 mRecycler.clearOldPositions();
3963 }
3964
3965 void offsetPositionRecordsForMove(int from, int to) {
3966 final int childCount = mChildHelper.getUnfilteredChildCount();
3967 final int start, end, inBetweenOffset;
3968 if (from < to) {
3969 start = from;
3970 end = to;
3971 inBetweenOffset = -1;
3972 } else {
3973 start = to;
3974 end = from;
3975 inBetweenOffset = 1;
3976 }
3977
3978 for (int i = 0; i < childCount; i++) {
3979 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
3980 if (holder == null || holder.mPosition < start || holder.mPosition > end) {
3981 continue;
3982 }
3983 if (DEBUG) {
3984 Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder "
3985 + holder);
3986 }
3987 if (holder.mPosition == from) {
3988 holder.offsetPosition(to - from, false);
3989 } else {
3990 holder.offsetPosition(inBetweenOffset, false);
3991 }
3992
3993 mState.mStructureChanged = true;
3994 }
3995 mRecycler.offsetPositionRecordsForMove(from, to);
3996 requestLayout();
3997 }
3998
3999 void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
4000 final int childCount = mChildHelper.getUnfilteredChildCount();
4001 for (int i = 0; i < childCount; i++) {
4002 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4003 if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
4004 if (DEBUG) {
4005 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
4006 + holder + " now at position " + (holder.mPosition + itemCount));
4007 }
4008 holder.offsetPosition(itemCount, false);
4009 mState.mStructureChanged = true;
4010 }
4011 }
4012 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
4013 requestLayout();
4014 }
4015
4016 void offsetPositionRecordsForRemove(int positionStart, int itemCount,
4017 boolean applyToPreLayout) {
4018 final int positionEnd = positionStart + itemCount;
4019 final int childCount = mChildHelper.getUnfilteredChildCount();
4020 for (int i = 0; i < childCount; i++) {
4021 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4022 if (holder != null && !holder.shouldIgnore()) {
4023 if (holder.mPosition >= positionEnd) {
4024 if (DEBUG) {
4025 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
4026 + " holder " + holder + " now at position "
4027 + (holder.mPosition - itemCount));
4028 }
4029 holder.offsetPosition(-itemCount, applyToPreLayout);
4030 mState.mStructureChanged = true;
4031 } else if (holder.mPosition >= positionStart) {
4032 if (DEBUG) {
4033 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
4034 + " holder " + holder + " now REMOVED");
4035 }
4036 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
4037 applyToPreLayout);
4038 mState.mStructureChanged = true;
4039 }
4040 }
4041 }
4042 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
4043 requestLayout();
4044 }
4045
4046 /**
4047 * Rebind existing views for the given range, or create as needed.
4048 *
4049 * @param positionStart Adapter position to start at
4050 * @param itemCount Number of views that must explicitly be rebound
4051 */
4052 void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
4053 final int childCount = mChildHelper.getUnfilteredChildCount();
4054 final int positionEnd = positionStart + itemCount;
4055
4056 for (int i = 0; i < childCount; i++) {
4057 final View child = mChildHelper.getUnfilteredChildAt(i);
4058 final ViewHolder holder = getChildViewHolderInt(child);
4059 if (holder == null || holder.shouldIgnore()) {
4060 continue;
4061 }
4062 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
4063 // We re-bind these view holders after pre-processing is complete so that
4064 // ViewHolders have their final positions assigned.
4065 holder.addFlags(ViewHolder.FLAG_UPDATE);
4066 holder.addChangePayload(payload);
4067 // lp cannot be null since we get ViewHolder from it.
4068 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
4069 }
4070 }
4071 mRecycler.viewRangeUpdate(positionStart, itemCount);
4072 }
4073
4074 boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
4075 return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
4076 viewHolder.getUnmodifiedPayloads());
4077 }
4078
4079
4080 /**
4081 * Call this method to signal that *all* adapter content has changed (generally, because of
4082 * swapAdapter, or notifyDataSetChanged), and that once layout occurs, all attached items should
4083 * be discarded or animated. Note that this work is deferred because RecyclerView requires a
4084 * layout to resolve non-incremental changes to the data set.
4085 *
4086 * Attached items are labeled as position unknown, and may no longer be cached.
4087 *
4088 * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
4089 * so calling this method *must* be associated with marking the cache invalid, so that the
4090 * only valid items that remain in the cache, once layout occurs, are prefetched items.
4091 */
4092 void setDataSetChangedAfterLayout() {
4093 if (mDataSetHasChangedAfterLayout) {
4094 return;
4095 }
4096 mDataSetHasChangedAfterLayout = true;
4097 final int childCount = mChildHelper.getUnfilteredChildCount();
4098 for (int i = 0; i < childCount; i++) {
4099 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4100 if (holder != null && !holder.shouldIgnore()) {
4101 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
4102 }
4103 }
4104 mRecycler.setAdapterPositionsAsUnknown();
4105
4106 // immediately mark all views as invalid, so prefetched views can be
4107 // differentiated from views bound to previous data set - both in children, and cache
4108 markKnownViewsInvalid();
4109 }
4110
4111 /**
4112 * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
4113 * data change event.
4114 */
4115 void markKnownViewsInvalid() {
4116 final int childCount = mChildHelper.getUnfilteredChildCount();
4117 for (int i = 0; i < childCount; i++) {
4118 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4119 if (holder != null && !holder.shouldIgnore()) {
4120 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
4121 }
4122 }
4123 markItemDecorInsetsDirty();
4124 mRecycler.markKnownViewsInvalid();
4125 }
4126
4127 /**
4128 * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method
4129 * will trigger a {@link #requestLayout()} call.
4130 */
4131 public void invalidateItemDecorations() {
4132 if (mItemDecorations.size() == 0) {
4133 return;
4134 }
4135 if (mLayout != null) {
4136 mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll"
4137 + " or layout");
4138 }
4139 markItemDecorInsetsDirty();
4140 requestLayout();
4141 }
4142
4143 /**
4144 * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
4145 * focus even if the View representing the Item is replaced during a layout calculation.
4146 * <p>
4147 * By default, this value is {@code true}.
4148 *
4149 * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
4150 * focus.
4151 *
4152 * @see #setPreserveFocusAfterLayout(boolean)
4153 */
4154 public boolean getPreserveFocusAfterLayout() {
4155 return mPreserveFocusAfterLayout;
4156 }
4157
4158 /**
4159 * Set whether the RecyclerView should try to keep the same Item focused after a layout
4160 * calculation or not.
4161 * <p>
4162 * Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
4163 * views may lose focus during a layout calculation as their state changes or they are replaced
4164 * with another view due to type change or animation. In these cases, RecyclerView can request
4165 * focus on the new view automatically.
4166 *
4167 * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
4168 * layout calculations. Defaults to true.
4169 *
4170 * @see #getPreserveFocusAfterLayout()
4171 */
4172 public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
4173 mPreserveFocusAfterLayout = preserveFocusAfterLayout;
4174 }
4175
4176 /**
4177 * Retrieve the {@link ViewHolder} for the given child view.
4178 *
4179 * @param child Child of this RecyclerView to query for its ViewHolder
4180 * @return The child view's ViewHolder
4181 */
4182 public ViewHolder getChildViewHolder(View child) {
4183 final ViewParent parent = child.getParent();
4184 if (parent != null && parent != this) {
4185 throw new IllegalArgumentException("View " + child + " is not a direct child of "
4186 + this);
4187 }
4188 return getChildViewHolderInt(child);
4189 }
4190
4191 /**
4192 * Traverses the ancestors of the given view and returns the item view that contains it and
4193 * also a direct child of the RecyclerView. This returned view can be used to get the
4194 * ViewHolder by calling {@link #getChildViewHolder(View)}.
4195 *
4196 * @param view The view that is a descendant of the RecyclerView.
4197 *
4198 * @return The direct child of the RecyclerView which contains the given view or null if the
4199 * provided view is not a descendant of this RecyclerView.
4200 *
4201 * @see #getChildViewHolder(View)
4202 * @see #findContainingViewHolder(View)
4203 */
4204 @Nullable
4205 public View findContainingItemView(View view) {
4206 ViewParent parent = view.getParent();
4207 while (parent != null && parent != this && parent instanceof View) {
4208 view = (View) parent;
4209 parent = view.getParent();
4210 }
4211 return parent == this ? view : null;
4212 }
4213
4214 /**
4215 * Returns the ViewHolder that contains the given view.
4216 *
4217 * @param view The view that is a descendant of the RecyclerView.
4218 *
4219 * @return The ViewHolder that contains the given view or null if the provided view is not a
4220 * descendant of this RecyclerView.
4221 */
4222 @Nullable
4223 public ViewHolder findContainingViewHolder(View view) {
4224 View itemView = findContainingItemView(view);
4225 return itemView == null ? null : getChildViewHolder(itemView);
4226 }
4227
4228
4229 static ViewHolder getChildViewHolderInt(View child) {
4230 if (child == null) {
4231 return null;
4232 }
4233 return ((LayoutParams) child.getLayoutParams()).mViewHolder;
4234 }
4235
4236 /**
4237 * @deprecated use {@link #getChildAdapterPosition(View)} or
4238 * {@link #getChildLayoutPosition(View)}.
4239 */
4240 @Deprecated
4241 public int getChildPosition(View child) {
4242 return getChildAdapterPosition(child);
4243 }
4244
4245 /**
4246 * Return the adapter position that the given child view corresponds to.
4247 *
4248 * @param child Child View to query
4249 * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
4250 */
4251 public int getChildAdapterPosition(View child) {
4252 final ViewHolder holder = getChildViewHolderInt(child);
4253 return holder != null ? holder.getAdapterPosition() : NO_POSITION;
4254 }
4255
4256 /**
4257 * Return the adapter position of the given child view as of the latest completed layout pass.
4258 * <p>
4259 * This position may not be equal to Item's adapter position if there are pending changes
4260 * in the adapter which have not been reflected to the layout yet.
4261 *
4262 * @param child Child View to query
4263 * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
4264 * the View is representing a removed item.
4265 */
4266 public int getChildLayoutPosition(View child) {
4267 final ViewHolder holder = getChildViewHolderInt(child);
4268 return holder != null ? holder.getLayoutPosition() : NO_POSITION;
4269 }
4270
4271 /**
4272 * Return the stable item id that the given child view corresponds to.
4273 *
4274 * @param child Child View to query
4275 * @return Item id corresponding to the given view or {@link #NO_ID}
4276 */
4277 public long getChildItemId(View child) {
4278 if (mAdapter == null || !mAdapter.hasStableIds()) {
4279 return NO_ID;
4280 }
4281 final ViewHolder holder = getChildViewHolderInt(child);
4282 return holder != null ? holder.getItemId() : NO_ID;
4283 }
4284
4285 /**
4286 * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or
4287 * {@link #findViewHolderForAdapterPosition(int)}
4288 */
4289 @Deprecated
4290 public ViewHolder findViewHolderForPosition(int position) {
4291 return findViewHolderForPosition(position, false);
4292 }
4293
4294 /**
4295 * Return the ViewHolder for the item in the given position of the data set as of the latest
4296 * layout pass.
4297 * <p>
4298 * This method checks only the children of RecyclerView. If the item at the given
4299 * <code>position</code> is not laid out, it <em>will not</em> create a new one.
4300 * <p>
4301 * Note that when Adapter contents change, ViewHolder positions are not updated until the
4302 * next layout calculation. If there are pending adapter updates, the return value of this
4303 * method may not match your adapter contents. You can use
4304 * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
4305 * <p>
4306 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
4307 * with the same layout position representing the same Item. In this case, the updated
4308 * ViewHolder will be returned.
4309 *
4310 * @param position The position of the item in the data set of the adapter
4311 * @return The ViewHolder at <code>position</code> or null if there is no such item
4312 */
4313 public ViewHolder findViewHolderForLayoutPosition(int position) {
4314 return findViewHolderForPosition(position, false);
4315 }
4316
4317 /**
4318 * Return the ViewHolder for the item in the given position of the data set. Unlike
4319 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
4320 * adapter changes that may not be reflected to the layout yet. On the other hand, if
4321 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
4322 * calculated yet, this method will return <code>null</code> since the new positions of views
4323 * are unknown until the layout is calculated.
4324 * <p>
4325 * This method checks only the children of RecyclerView. If the item at the given
4326 * <code>position</code> is not laid out, it <em>will not</em> create a new one.
4327 * <p>
4328 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
4329 * representing the same Item. In this case, the updated ViewHolder will be returned.
4330 *
4331 * @param position The position of the item in the data set of the adapter
4332 * @return The ViewHolder at <code>position</code> or null if there is no such item
4333 */
4334 public ViewHolder findViewHolderForAdapterPosition(int position) {
4335 if (mDataSetHasChangedAfterLayout) {
4336 return null;
4337 }
4338 final int childCount = mChildHelper.getUnfilteredChildCount();
4339 // hidden VHs are not preferred but if that is the only one we find, we rather return it
4340 ViewHolder hidden = null;
4341 for (int i = 0; i < childCount; i++) {
4342 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4343 if (holder != null && !holder.isRemoved()
4344 && getAdapterPositionFor(holder) == position) {
4345 if (mChildHelper.isHidden(holder.itemView)) {
4346 hidden = holder;
4347 } else {
4348 return holder;
4349 }
4350 }
4351 }
4352 return hidden;
4353 }
4354
4355 ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
4356 final int childCount = mChildHelper.getUnfilteredChildCount();
4357 ViewHolder hidden = null;
4358 for (int i = 0; i < childCount; i++) {
4359 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4360 if (holder != null && !holder.isRemoved()) {
4361 if (checkNewPosition) {
4362 if (holder.mPosition != position) {
4363 continue;
4364 }
4365 } else if (holder.getLayoutPosition() != position) {
4366 continue;
4367 }
4368 if (mChildHelper.isHidden(holder.itemView)) {
4369 hidden = holder;
4370 } else {
4371 return holder;
4372 }
4373 }
4374 }
4375 // This method should not query cached views. It creates a problem during adapter updates
4376 // when we are dealing with already laid out views. Also, for the public method, it is more
4377 // reasonable to return null if position is not laid out.
4378 return hidden;
4379 }
4380
4381 /**
4382 * Return the ViewHolder for the item with the given id. The RecyclerView must
4383 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
4384 * return a non-null value.
4385 * <p>
4386 * This method checks only the children of RecyclerView. If the item with the given
4387 * <code>id</code> is not laid out, it <em>will not</em> create a new one.
4388 *
4389 * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the
4390 * same id. In this case, the updated ViewHolder will be returned.
4391 *
4392 * @param id The id for the requested item
4393 * @return The ViewHolder with the given <code>id</code> or null if there is no such item
4394 */
4395 public ViewHolder findViewHolderForItemId(long id) {
4396 if (mAdapter == null || !mAdapter.hasStableIds()) {
4397 return null;
4398 }
4399 final int childCount = mChildHelper.getUnfilteredChildCount();
4400 ViewHolder hidden = null;
4401 for (int i = 0; i < childCount; i++) {
4402 final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
4403 if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
4404 if (mChildHelper.isHidden(holder.itemView)) {
4405 hidden = holder;
4406 } else {
4407 return holder;
4408 }
4409 }
4410 }
4411 return hidden;
4412 }
4413
4414 /**
4415 * Find the topmost view under the given point.
4416 *
4417 * @param x Horizontal position in pixels to search
4418 * @param y Vertical position in pixels to search
4419 * @return The child view under (x, y) or null if no matching child is found
4420 */
4421 public View findChildViewUnder(float x, float y) {
4422 final int count = mChildHelper.getChildCount();
4423 for (int i = count - 1; i >= 0; i--) {
4424 final View child = mChildHelper.getChildAt(i);
4425 final float translationX = child.getTranslationX();
4426 final float translationY = child.getTranslationY();
4427 if (x >= child.getLeft() + translationX
4428 && x <= child.getRight() + translationX
4429 && y >= child.getTop() + translationY
4430 && y <= child.getBottom() + translationY) {
4431 return child;
4432 }
4433 }
4434 return null;
4435 }
4436
4437 @Override
4438 public boolean drawChild(Canvas canvas, View child, long drawingTime) {
4439 return super.drawChild(canvas, child, drawingTime);
4440 }
4441
4442 /**
4443 * Offset the bounds of all child views by <code>dy</code> pixels.
4444 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
4445 *
4446 * @param dy Vertical pixel offset to apply to the bounds of all child views
4447 */
4448 public void offsetChildrenVertical(int dy) {
4449 final int childCount = mChildHelper.getChildCount();
4450 for (int i = 0; i < childCount; i++) {
4451 mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
4452 }
4453 }
4454
4455 /**
4456 * Called when an item view is attached to this RecyclerView.
4457 *
4458 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
4459 * of child views as they become attached. This will be called before a
4460 * {@link LayoutManager} measures or lays out the view and is a good time to perform these
4461 * changes.</p>
4462 *
4463 * @param child Child view that is now attached to this RecyclerView and its associated window
4464 */
4465 public void onChildAttachedToWindow(View child) {
4466 }
4467
4468 /**
4469 * Called when an item view is detached from this RecyclerView.
4470 *
4471 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
4472 * of child views as they become detached. This will be called as a
4473 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p>
4474 *
4475 * @param child Child view that is now detached from this RecyclerView and its associated window
4476 */
4477 public void onChildDetachedFromWindow(View child) {
4478 }
4479
4480 /**
4481 * Offset the bounds of all child views by <code>dx</code> pixels.
4482 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
4483 *
4484 * @param dx Horizontal pixel offset to apply to the bounds of all child views
4485 */
4486 public void offsetChildrenHorizontal(int dx) {
4487 final int childCount = mChildHelper.getChildCount();
4488 for (int i = 0; i < childCount; i++) {
4489 mChildHelper.getChildAt(i).offsetLeftAndRight(dx);
4490 }
4491 }
4492
4493 /**
4494 * Returns the bounds of the view including its decoration and margins.
4495 *
4496 * @param view The view element to check
4497 * @param outBounds A rect that will receive the bounds of the element including its
4498 * decoration and margins.
4499 */
4500 public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
4501 getDecoratedBoundsWithMarginsInt(view, outBounds);
4502 }
4503
4504 static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) {
4505 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
4506 final Rect insets = lp.mDecorInsets;
4507 outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
4508 view.getTop() - insets.top - lp.topMargin,
4509 view.getRight() + insets.right + lp.rightMargin,
4510 view.getBottom() + insets.bottom + lp.bottomMargin);
4511 }
4512
4513 Rect getItemDecorInsetsForChild(View child) {
4514 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
4515 if (!lp.mInsetsDirty) {
4516 return lp.mDecorInsets;
4517 }
4518
4519 if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
4520 // changed/invalid items should not be updated until they are rebound.
4521 return lp.mDecorInsets;
4522 }
4523 final Rect insets = lp.mDecorInsets;
4524 insets.set(0, 0, 0, 0);
4525 final int decorCount = mItemDecorations.size();
4526 for (int i = 0; i < decorCount; i++) {
4527 mTempRect.set(0, 0, 0, 0);
4528 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
4529 insets.left += mTempRect.left;
4530 insets.top += mTempRect.top;
4531 insets.right += mTempRect.right;
4532 insets.bottom += mTempRect.bottom;
4533 }
4534 lp.mInsetsDirty = false;
4535 return insets;
4536 }
4537
4538 /**
4539 * Called when the scroll position of this RecyclerView changes. Subclasses should use
4540 * this method to respond to scrolling within the adapter's data set instead of an explicit
4541 * listener.
4542 *
4543 * <p>This method will always be invoked before listeners. If a subclass needs to perform
4544 * any additional upkeep or bookkeeping after scrolling but before listeners run,
4545 * this is a good place to do so.</p>
4546 *
4547 * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives
4548 * the distance scrolled in either direction within the adapter's data set instead of absolute
4549 * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from
4550 * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive
4551 * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which
4552 * do not correspond to the data set scroll position. However, some subclasses may choose
4553 * to use these fields as special offsets.</p>
4554 *
4555 * @param dx horizontal distance scrolled in pixels
4556 * @param dy vertical distance scrolled in pixels
4557 */
4558 public void onScrolled(int dx, int dy) {
4559 // Do nothing
4560 }
4561
4562 void dispatchOnScrolled(int hresult, int vresult) {
4563 mDispatchScrollCounter++;
4564 // Pass the current scrollX/scrollY values; no actual change in these properties occurred
4565 // but some general-purpose code may choose to respond to changes this way.
4566 final int scrollX = getScrollX();
4567 final int scrollY = getScrollY();
4568 onScrollChanged(scrollX, scrollY, scrollX, scrollY);
4569
4570 // Pass the real deltas to onScrolled, the RecyclerView-specific method.
4571 onScrolled(hresult, vresult);
4572
4573 // Invoke listeners last. Subclassed view methods always handle the event first.
4574 // All internal state is consistent by the time listeners are invoked.
4575 if (mScrollListener != null) {
4576 mScrollListener.onScrolled(this, hresult, vresult);
4577 }
4578 if (mScrollListeners != null) {
4579 for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
4580 mScrollListeners.get(i).onScrolled(this, hresult, vresult);
4581 }
4582 }
4583 mDispatchScrollCounter--;
4584 }
4585
4586 /**
4587 * Called when the scroll state of this RecyclerView changes. Subclasses should use this
4588 * method to respond to state changes instead of an explicit listener.
4589 *
4590 * <p>This method will always be invoked before listeners, but after the LayoutManager
4591 * responds to the scroll state change.</p>
4592 *
4593 * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE},
4594 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}
4595 */
4596 public void onScrollStateChanged(int state) {
4597 // Do nothing
4598 }
4599
4600 void dispatchOnScrollStateChanged(int state) {
4601 // Let the LayoutManager go first; this allows it to bring any properties into
4602 // a consistent state before the RecyclerView subclass responds.
4603 if (mLayout != null) {
4604 mLayout.onScrollStateChanged(state);
4605 }
4606
4607 // Let the RecyclerView subclass handle this event next; any LayoutManager property
4608 // changes will be reflected by this time.
4609 onScrollStateChanged(state);
4610
4611 // Listeners go last. All other internal state is consistent by this point.
4612 if (mScrollListener != null) {
4613 mScrollListener.onScrollStateChanged(this, state);
4614 }
4615 if (mScrollListeners != null) {
4616 for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
4617 mScrollListeners.get(i).onScrollStateChanged(this, state);
4618 }
4619 }
4620 }
4621
4622 /**
4623 * Returns whether there are pending adapter updates which are not yet applied to the layout.
4624 * <p>
4625 * If this method returns <code>true</code>, it means that what user is currently seeing may not
4626 * reflect them adapter contents (depending on what has changed).
4627 * You may use this information to defer or cancel some operations.
4628 * <p>
4629 * This method returns true if RecyclerView has not yet calculated the first layout after it is
4630 * attached to the Window or the Adapter has been replaced.
4631 *
4632 * @return True if there are some adapter updates which are not yet reflected to layout or false
4633 * if layout is up to date.
4634 */
4635 public boolean hasPendingAdapterUpdates() {
4636 return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout
4637 || mAdapterHelper.hasPendingUpdates();
4638 }
4639
4640 class ViewFlinger implements Runnable {
4641 private int mLastFlingX;
4642 private int mLastFlingY;
4643 private OverScroller mScroller;
4644 Interpolator mInterpolator = sQuinticInterpolator;
4645
4646
4647 // When set to true, postOnAnimation callbacks are delayed until the run method completes
4648 private boolean mEatRunOnAnimationRequest = false;
4649
4650 // Tracks if postAnimationCallback should be re-attached when it is done
4651 private boolean mReSchedulePostAnimationCallback = false;
4652
4653 ViewFlinger() {
4654 mScroller = new OverScroller(getContext(), sQuinticInterpolator);
4655 }
4656
4657 @Override
4658 public void run() {
4659 if (mLayout == null) {
4660 stop();
4661 return; // no layout, cannot scroll.
4662 }
4663 disableRunOnAnimationRequests();
4664 consumePendingUpdateOperations();
4665 // keep a local reference so that if it is changed during onAnimation method, it won't
4666 // cause unexpected behaviors
4667 final OverScroller scroller = mScroller;
4668 final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
4669 if (scroller.computeScrollOffset()) {
4670 final int x = scroller.getCurrX();
4671 final int y = scroller.getCurrY();
4672 final int dx = x - mLastFlingX;
4673 final int dy = y - mLastFlingY;
4674 int hresult = 0;
4675 int vresult = 0;
4676 mLastFlingX = x;
4677 mLastFlingY = y;
4678 int overscrollX = 0, overscrollY = 0;
4679 if (mAdapter != null) {
4680 eatRequestLayout();
4681 onEnterLayoutOrScroll();
4682 Trace.beginSection(TRACE_SCROLL_TAG);
4683 if (dx != 0) {
4684 hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
4685 overscrollX = dx - hresult;
4686 }
4687 if (dy != 0) {
4688 vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
4689 overscrollY = dy - vresult;
4690 }
4691 Trace.endSection();
4692 repositionShadowingViews();
4693
4694 onExitLayoutOrScroll();
4695 resumeRequestLayout(false);
4696
4697 if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
4698 && smoothScroller.isRunning()) {
4699 final int adapterSize = mState.getItemCount();
4700 if (adapterSize == 0) {
4701 smoothScroller.stop();
4702 } else if (smoothScroller.getTargetPosition() >= adapterSize) {
4703 smoothScroller.setTargetPosition(adapterSize - 1);
4704 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
4705 } else {
4706 smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
4707 }
4708 }
4709 }
4710 if (!mItemDecorations.isEmpty()) {
4711 invalidate();
4712 }
4713 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
4714 considerReleasingGlowsOnScroll(dx, dy);
4715 }
4716 if (overscrollX != 0 || overscrollY != 0) {
4717 final int vel = (int) scroller.getCurrVelocity();
4718
4719 int velX = 0;
4720 if (overscrollX != x) {
4721 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
4722 }
4723
4724 int velY = 0;
4725 if (overscrollY != y) {
4726 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
4727 }
4728
4729 if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
4730 absorbGlows(velX, velY);
4731 }
4732 if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0)
4733 && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
4734 scroller.abortAnimation();
4735 }
4736 }
4737 if (hresult != 0 || vresult != 0) {
4738 dispatchOnScrolled(hresult, vresult);
4739 }
4740
4741 if (!awakenScrollBars()) {
4742 invalidate();
4743 }
4744
4745 final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
4746 && vresult == dy;
4747 final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
4748 && hresult == dx;
4749 final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
4750 || fullyConsumedVertical;
4751
4752 if (scroller.isFinished() || !fullyConsumedAny) {
4753 setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
4754 if (ALLOW_THREAD_GAP_WORK) {
4755 mPrefetchRegistry.clearPrefetchPositions();
4756 }
4757 } else {
4758 postOnAnimation();
4759 if (mGapWorker != null) {
4760 mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
4761 }
4762 }
4763 }
4764 // call this after the onAnimation is complete not to have inconsistent callbacks etc.
4765 if (smoothScroller != null) {
4766 if (smoothScroller.isPendingInitialRun()) {
4767 smoothScroller.onAnimation(0, 0);
4768 }
4769 if (!mReSchedulePostAnimationCallback) {
4770 smoothScroller.stop(); //stop if it does not trigger any scroll
4771 }
4772 }
4773 enableRunOnAnimationRequests();
4774 }
4775
4776 private void disableRunOnAnimationRequests() {
4777 mReSchedulePostAnimationCallback = false;
4778 mEatRunOnAnimationRequest = true;
4779 }
4780
4781 private void enableRunOnAnimationRequests() {
4782 mEatRunOnAnimationRequest = false;
4783 if (mReSchedulePostAnimationCallback) {
4784 postOnAnimation();
4785 }
4786 }
4787
4788 void postOnAnimation() {
4789 if (mEatRunOnAnimationRequest) {
4790 mReSchedulePostAnimationCallback = true;
4791 } else {
4792 removeCallbacks(this);
4793 RecyclerView.this.postOnAnimation(this);
4794 }
4795 }
4796
4797 public void fling(int velocityX, int velocityY) {
4798 setScrollState(SCROLL_STATE_SETTLING);
4799 mLastFlingX = mLastFlingY = 0;
4800 mScroller.fling(0, 0, velocityX, velocityY,
4801 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
4802 postOnAnimation();
4803 }
4804
4805 public void smoothScrollBy(int dx, int dy) {
4806 smoothScrollBy(dx, dy, 0, 0);
4807 }
4808
4809 public void smoothScrollBy(int dx, int dy, int vx, int vy) {
4810 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
4811 }
4812
4813 private float distanceInfluenceForSnapDuration(float f) {
4814 f -= 0.5f; // center the values about 0.
4815 f *= 0.3f * Math.PI / 2.0f;
4816 return (float) Math.sin(f);
4817 }
4818
4819 private int computeScrollDuration(int dx, int dy, int vx, int vy) {
4820 final int absDx = Math.abs(dx);
4821 final int absDy = Math.abs(dy);
4822 final boolean horizontal = absDx > absDy;
4823 final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
4824 final int delta = (int) Math.sqrt(dx * dx + dy * dy);
4825 final int containerSize = horizontal ? getWidth() : getHeight();
4826 final int halfContainerSize = containerSize / 2;
4827 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
4828 final float distance = halfContainerSize + halfContainerSize
4829 * distanceInfluenceForSnapDuration(distanceRatio);
4830
4831 final int duration;
4832 if (velocity > 0) {
4833 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
4834 } else {
4835 float absDelta = (float) (horizontal ? absDx : absDy);
4836 duration = (int) (((absDelta / containerSize) + 1) * 300);
4837 }
4838 return Math.min(duration, MAX_SCROLL_DURATION);
4839 }
4840
4841 public void smoothScrollBy(int dx, int dy, int duration) {
4842 smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
4843 }
4844
4845 public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
4846 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0),
4847 interpolator == null ? sQuinticInterpolator : interpolator);
4848 }
4849
4850 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
4851 if (mInterpolator != interpolator) {
4852 mInterpolator = interpolator;
4853 mScroller = new OverScroller(getContext(), interpolator);
4854 }
4855 setScrollState(SCROLL_STATE_SETTLING);
4856 mLastFlingX = mLastFlingY = 0;
4857 mScroller.startScroll(0, 0, dx, dy, duration);
4858 postOnAnimation();
4859 }
4860
4861 public void stop() {
4862 removeCallbacks(this);
4863 mScroller.abortAnimation();
4864 }
4865
4866 }
4867
4868 void repositionShadowingViews() {
4869 // Fix up shadow views used by change animations
4870 int count = mChildHelper.getChildCount();
4871 for (int i = 0; i < count; i++) {
4872 View view = mChildHelper.getChildAt(i);
4873 ViewHolder holder = getChildViewHolder(view);
4874 if (holder != null && holder.mShadowingHolder != null) {
4875 View shadowingView = holder.mShadowingHolder.itemView;
4876 int left = view.getLeft();
4877 int top = view.getTop();
4878 if (left != shadowingView.getLeft() || top != shadowingView.getTop()) {
4879 shadowingView.layout(left, top,
4880 left + shadowingView.getWidth(),
4881 top + shadowingView.getHeight());
4882 }
4883 }
4884 }
4885 }
4886
4887 private class RecyclerViewDataObserver extends AdapterDataObserver {
4888 RecyclerViewDataObserver() {
4889 }
4890
4891 @Override
4892 public void onChanged() {
4893 assertNotInLayoutOrScroll(null);
4894 mState.mStructureChanged = true;
4895
4896 setDataSetChangedAfterLayout();
4897 if (!mAdapterHelper.hasPendingUpdates()) {
4898 requestLayout();
4899 }
4900 }
4901
4902 @Override
4903 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
4904 assertNotInLayoutOrScroll(null);
4905 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
4906 triggerUpdateProcessor();
4907 }
4908 }
4909
4910 @Override
4911 public void onItemRangeInserted(int positionStart, int itemCount) {
4912 assertNotInLayoutOrScroll(null);
4913 if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
4914 triggerUpdateProcessor();
4915 }
4916 }
4917
4918 @Override
4919 public void onItemRangeRemoved(int positionStart, int itemCount) {
4920 assertNotInLayoutOrScroll(null);
4921 if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
4922 triggerUpdateProcessor();
4923 }
4924 }
4925
4926 @Override
4927 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
4928 assertNotInLayoutOrScroll(null);
4929 if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
4930 triggerUpdateProcessor();
4931 }
4932 }
4933
4934 void triggerUpdateProcessor() {
4935 if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
4936 RecyclerView.this.postOnAnimation(mUpdateChildViewsRunnable);
4937 } else {
4938 mAdapterUpdateDuringMeasure = true;
4939 requestLayout();
4940 }
4941 }
4942 }
4943
4944 /**
4945 * RecycledViewPool lets you share Views between multiple RecyclerViews.
4946 * <p>
4947 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
4948 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
4949 * <p>
4950 * RecyclerView automatically creates a pool for itself if you don't provide one.
4951 *
4952 */
4953 public static class RecycledViewPool {
4954 private static final int DEFAULT_MAX_SCRAP = 5;
4955
4956 /**
4957 * Tracks both pooled holders, as well as create/bind timing metadata for the given type.
4958 *
4959 * Note that this tracks running averages of create/bind time across all RecyclerViews
4960 * (and, indirectly, Adapters) that use this pool.
4961 *
4962 * 1) This enables us to track average create and bind times across multiple adapters. Even
4963 * though create (and especially bind) may behave differently for different Adapter
4964 * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
4965 *
4966 * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
4967 * false for all other views of its type for the same deadline. This prevents items
4968 * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
4969 */
4970 static class ScrapData {
Andrei Onea15884392019-03-22 17:28:11 +00004971 @UnsupportedAppUsage
Aurimas Liutikas7149a632017-01-18 17:36:10 -08004972 ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
4973 int mMaxScrap = DEFAULT_MAX_SCRAP;
4974 long mCreateRunningAverageNs = 0;
4975 long mBindRunningAverageNs = 0;
4976 }
4977 SparseArray<ScrapData> mScrap = new SparseArray<>();
4978
4979 private int mAttachCount = 0;
4980
4981 public void clear() {
4982 for (int i = 0; i < mScrap.size(); i++) {
4983 ScrapData data = mScrap.valueAt(i);
4984 data.mScrapHeap.clear();
4985 }
4986 }
4987
4988 public void setMaxRecycledViews(int viewType, int max) {
4989 ScrapData scrapData = getScrapDataForType(viewType);
4990 scrapData.mMaxScrap = max;
4991 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
4992 if (scrapHeap != null) {
4993 while (scrapHeap.size() > max) {
4994 scrapHeap.remove(scrapHeap.size() - 1);
4995 }
4996 }
4997 }
4998
4999 /**
5000 * Returns the current number of Views held by the RecycledViewPool of the given view type.
5001 */
5002 public int getRecycledViewCount(int viewType) {
5003 return getScrapDataForType(viewType).mScrapHeap.size();
5004 }
5005
5006 public ViewHolder getRecycledView(int viewType) {
5007 final ScrapData scrapData = mScrap.get(viewType);
5008 if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
5009 final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
5010 return scrapHeap.remove(scrapHeap.size() - 1);
5011 }
5012 return null;
5013 }
5014
5015 int size() {
5016 int count = 0;
5017 for (int i = 0; i < mScrap.size(); i++) {
5018 ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap;
5019 if (viewHolders != null) {
5020 count += viewHolders.size();
5021 }
5022 }
5023 return count;
5024 }
5025
5026 public void putRecycledView(ViewHolder scrap) {
5027 final int viewType = scrap.getItemViewType();
5028 final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
5029 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
5030 return;
5031 }
5032 if (DEBUG && scrapHeap.contains(scrap)) {
5033 throw new IllegalArgumentException("this scrap item already exists");
5034 }
5035 scrap.resetInternal();
5036 scrapHeap.add(scrap);
5037 }
5038
5039 long runningAverage(long oldAverage, long newValue) {
5040 if (oldAverage == 0) {
5041 return newValue;
5042 }
5043 return (oldAverage / 4 * 3) + (newValue / 4);
5044 }
5045
5046 void factorInCreateTime(int viewType, long createTimeNs) {
5047 ScrapData scrapData = getScrapDataForType(viewType);
5048 scrapData.mCreateRunningAverageNs = runningAverage(
5049 scrapData.mCreateRunningAverageNs, createTimeNs);
5050 }
5051
5052 void factorInBindTime(int viewType, long bindTimeNs) {
5053 ScrapData scrapData = getScrapDataForType(viewType);
5054 scrapData.mBindRunningAverageNs = runningAverage(
5055 scrapData.mBindRunningAverageNs, bindTimeNs);
5056 }
5057
5058 boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
5059 long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
5060 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
5061 }
5062
5063 boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
5064 long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs;
5065 return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
5066 }
5067
5068 void attach(Adapter adapter) {
5069 mAttachCount++;
5070 }
5071
5072 void detach() {
5073 mAttachCount--;
5074 }
5075
5076
5077 /**
5078 * Detaches the old adapter and attaches the new one.
5079 * <p>
5080 * RecycledViewPool will clear its cache if it has only one adapter attached and the new
5081 * adapter uses a different ViewHolder than the oldAdapter.
5082 *
5083 * @param oldAdapter The previous adapter instance. Will be detached.
5084 * @param newAdapter The new adapter instance. Will be attached.
5085 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
5086 * ViewHolder and view types.
5087 */
5088 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
5089 boolean compatibleWithPrevious) {
5090 if (oldAdapter != null) {
5091 detach();
5092 }
5093 if (!compatibleWithPrevious && mAttachCount == 0) {
5094 clear();
5095 }
5096 if (newAdapter != null) {
5097 attach(newAdapter);
5098 }
5099 }
5100
5101 private ScrapData getScrapDataForType(int viewType) {
5102 ScrapData scrapData = mScrap.get(viewType);
5103 if (scrapData == null) {
5104 scrapData = new ScrapData();
5105 mScrap.put(viewType, scrapData);
5106 }
5107 return scrapData;
5108 }
5109 }
5110
5111 /**
5112 * Utility method for finding an internal RecyclerView, if present
5113 */
5114 @Nullable
5115 static RecyclerView findNestedRecyclerView(@NonNull View view) {
5116 if (!(view instanceof ViewGroup)) {
5117 return null;
5118 }
5119 if (view instanceof RecyclerView) {
5120 return (RecyclerView) view;
5121 }
5122 final ViewGroup parent = (ViewGroup) view;
5123 final int count = parent.getChildCount();
5124 for (int i = 0; i < count; i++) {
5125 final View child = parent.getChildAt(i);
5126 final RecyclerView descendant = findNestedRecyclerView(child);
5127 if (descendant != null) {
5128 return descendant;
5129 }
5130 }
5131 return null;
5132 }
5133
5134 /**
5135 * Utility method for clearing holder's internal RecyclerView, if present
5136 */
5137 static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) {
5138 if (holder.mNestedRecyclerView != null) {
5139 View item = holder.mNestedRecyclerView.get();
5140 while (item != null) {
5141 if (item == holder.itemView) {
5142 return; // match found, don't need to clear
5143 }
5144
5145 ViewParent parent = item.getParent();
5146 if (parent instanceof View) {
5147 item = (View) parent;
5148 } else {
5149 item = null;
5150 }
5151 }
5152 holder.mNestedRecyclerView = null; // not nested
5153 }
5154 }
5155
5156 /**
5157 * Time base for deadline-aware work scheduling. Overridable for testing.
5158 *
5159 * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling
5160 * isn't relevant.
5161 */
5162 long getNanoTime() {
5163 if (ALLOW_THREAD_GAP_WORK) {
5164 return System.nanoTime();
5165 } else {
5166 return 0;
5167 }
5168 }
5169
5170 /**
5171 * A Recycler is responsible for managing scrapped or detached item views for reuse.
5172 *
5173 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
5174 * that has been marked for removal or reuse.</p>
5175 *
5176 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
5177 * an adapter's data set representing the data at a given position or item ID.
5178 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
5179 * If not, the view can be quickly reused by the LayoutManager with no further work.
5180 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
5181 * may be repositioned by a LayoutManager without remeasurement.</p>
5182 */
5183 public final class Recycler {
5184 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
5185 ArrayList<ViewHolder> mChangedScrap = null;
5186
5187 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
5188
5189 private final List<ViewHolder>
5190 mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
5191
5192 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
5193 int mViewCacheMax = DEFAULT_CACHE_SIZE;
5194
5195 RecycledViewPool mRecyclerPool;
5196
5197 private ViewCacheExtension mViewCacheExtension;
5198
5199 static final int DEFAULT_CACHE_SIZE = 2;
5200
5201 /**
5202 * Clear scrap views out of this recycler. Detached views contained within a
5203 * recycled view pool will remain.
5204 */
5205 public void clear() {
5206 mAttachedScrap.clear();
5207 recycleAndClearCachedViews();
5208 }
5209
5210 /**
5211 * Set the maximum number of detached, valid views we should retain for later use.
5212 *
5213 * @param viewCount Number of views to keep before sending views to the shared pool
5214 */
5215 public void setViewCacheSize(int viewCount) {
5216 mRequestedCacheMax = viewCount;
5217 updateViewCacheSize();
5218 }
5219
5220 void updateViewCacheSize() {
5221 int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
5222 mViewCacheMax = mRequestedCacheMax + extraCache;
5223
5224 // first, try the views that can be recycled
5225 for (int i = mCachedViews.size() - 1;
5226 i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
5227 recycleCachedViewAt(i);
5228 }
5229 }
5230
5231 /**
5232 * Returns an unmodifiable list of ViewHolders that are currently in the scrap list.
5233 *
5234 * @return List of ViewHolders in the scrap list.
5235 */
5236 public List<ViewHolder> getScrapList() {
5237 return mUnmodifiableAttachedScrap;
5238 }
5239
5240 /**
5241 * Helper method for getViewForPosition.
5242 * <p>
5243 * Checks whether a given view holder can be used for the provided position.
5244 *
5245 * @param holder ViewHolder
5246 * @return true if ViewHolder matches the provided position, false otherwise
5247 */
5248 boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
5249 // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
5250 // if it is not removed, verify the type and id.
5251 if (holder.isRemoved()) {
5252 if (DEBUG && !mState.isPreLayout()) {
5253 throw new IllegalStateException("should not receive a removed view unless it"
5254 + " is pre layout");
5255 }
5256 return mState.isPreLayout();
5257 }
5258 if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
5259 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
5260 + "adapter position" + holder);
5261 }
5262 if (!mState.isPreLayout()) {
5263 // don't check type if it is pre-layout.
5264 final int type = mAdapter.getItemViewType(holder.mPosition);
5265 if (type != holder.getItemViewType()) {
5266 return false;
5267 }
5268 }
5269 if (mAdapter.hasStableIds()) {
5270 return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
5271 }
5272 return true;
5273 }
5274
5275 /**
5276 * Attempts to bind view, and account for relevant timing information. If
5277 * deadlineNs != FOREVER_NS, this method may fail to bind, and return false.
5278 *
5279 * @param holder Holder to be bound.
5280 * @param offsetPosition Position of item to be bound.
5281 * @param position Pre-layout position of item to be bound.
5282 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
5283 * complete. If FOREVER_NS is passed, this method will not fail to
5284 * bind the holder.
5285 * @return
5286 */
5287 private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
5288 int position, long deadlineNs) {
5289 holder.mOwnerRecyclerView = RecyclerView.this;
5290 final int viewType = holder.getItemViewType();
5291 long startBindNs = getNanoTime();
5292 if (deadlineNs != FOREVER_NS
5293 && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
5294 // abort - we have a deadline we can't meet
5295 return false;
5296 }
5297 mAdapter.bindViewHolder(holder, offsetPosition);
5298 long endBindNs = getNanoTime();
5299 mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
5300 attachAccessibilityDelegate(holder.itemView);
5301 if (mState.isPreLayout()) {
5302 holder.mPreLayoutPosition = position;
5303 }
5304 return true;
5305 }
5306
5307 /**
5308 * Binds the given View to the position. The View can be a View previously retrieved via
5309 * {@link #getViewForPosition(int)} or created by
5310 * {@link Adapter#onCreateViewHolder(ViewGroup, int)}.
5311 * <p>
5312 * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)}
5313 * and let the RecyclerView handle caching. This is a helper method for LayoutManager who
5314 * wants to handle its own recycling logic.
5315 * <p>
5316 * Note that, {@link #getViewForPosition(int)} already binds the View to the position so
5317 * you don't need to call this method unless you want to bind this View to another position.
5318 *
5319 * @param view The view to update.
5320 * @param position The position of the item to bind to this View.
5321 */
5322 public void bindViewToPosition(View view, int position) {
5323 ViewHolder holder = getChildViewHolderInt(view);
5324 if (holder == null) {
5325 throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
5326 + " pass arbitrary views to this method, they should be created by the "
5327 + "Adapter");
5328 }
5329 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
5330 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
5331 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
5332 + "position " + position + "(offset:" + offsetPosition + ")."
5333 + "state:" + mState.getItemCount());
5334 }
5335 tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);
5336
5337 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
5338 final LayoutParams rvLayoutParams;
5339 if (lp == null) {
5340 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
5341 holder.itemView.setLayoutParams(rvLayoutParams);
5342 } else if (!checkLayoutParams(lp)) {
5343 rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
5344 holder.itemView.setLayoutParams(rvLayoutParams);
5345 } else {
5346 rvLayoutParams = (LayoutParams) lp;
5347 }
5348
5349 rvLayoutParams.mInsetsDirty = true;
5350 rvLayoutParams.mViewHolder = holder;
5351 rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null;
5352 }
5353
5354 /**
5355 * RecyclerView provides artificial position range (item count) in pre-layout state and
5356 * automatically maps these positions to {@link Adapter} positions when
5357 * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called.
5358 * <p>
5359 * Usually, LayoutManager does not need to worry about this. However, in some cases, your
5360 * LayoutManager may need to call some custom component with item positions in which
5361 * case you need the actual adapter position instead of the pre layout position. You
5362 * can use this method to convert a pre-layout position to adapter (post layout) position.
5363 * <p>
5364 * Note that if the provided position belongs to a deleted ViewHolder, this method will
5365 * return -1.
5366 * <p>
5367 * Calling this method in post-layout state returns the same value back.
5368 *
5369 * @param position The pre-layout position to convert. Must be greater or equal to 0 and
5370 * less than {@link State#getItemCount()}.
5371 */
5372 public int convertPreLayoutPositionToPostLayout(int position) {
5373 if (position < 0 || position >= mState.getItemCount()) {
5374 throw new IndexOutOfBoundsException("invalid position " + position + ". State "
5375 + "item count is " + mState.getItemCount());
5376 }
5377 if (!mState.isPreLayout()) {
5378 return position;
5379 }
5380 return mAdapterHelper.findPositionOffset(position);
5381 }
5382
5383 /**
5384 * Obtain a view initialized for the given position.
5385 *
5386 * This method should be used by {@link LayoutManager} implementations to obtain
5387 * views to represent data from an {@link Adapter}.
5388 * <p>
5389 * The Recycler may reuse a scrap or detached view from a shared pool if one is
5390 * available for the correct view type. If the adapter has not indicated that the
5391 * data at the given position has changed, the Recycler will attempt to hand back
5392 * a scrap view that was previously initialized for that data without rebinding.
5393 *
5394 * @param position Position to obtain a view for
5395 * @return A view representing the data at <code>position</code> from <code>adapter</code>
5396 */
5397 public View getViewForPosition(int position) {
5398 return getViewForPosition(position, false);
5399 }
5400
5401 View getViewForPosition(int position, boolean dryRun) {
5402 return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
5403 }
5404
5405 /**
5406 * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
5407 * cache, the RecycledViewPool, or creating it directly.
5408 * <p>
5409 * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
5410 * rather than constructing or binding a ViewHolder if it doesn't think it has time.
5411 * If a ViewHolder must be constructed and not enough time remains, null is returned. If a
5412 * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
5413 * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
5414 *
5415 * @param position Position of ViewHolder to be returned.
5416 * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
5417 * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
5418 * complete. If FOREVER_NS is passed, this method will not fail to
5419 * create/bind the holder if needed.
5420 *
5421 * @return ViewHolder for requested position
5422 */
5423 @Nullable
5424 ViewHolder tryGetViewHolderForPositionByDeadline(int position,
5425 boolean dryRun, long deadlineNs) {
5426 if (position < 0 || position >= mState.getItemCount()) {
5427 throw new IndexOutOfBoundsException("Invalid item position " + position
5428 + "(" + position + "). Item count:" + mState.getItemCount());
5429 }
5430 boolean fromScrapOrHiddenOrCache = false;
5431 ViewHolder holder = null;
5432 // 0) If there is a changed scrap, try to find from there
5433 if (mState.isPreLayout()) {
5434 holder = getChangedScrapViewForPosition(position);
5435 fromScrapOrHiddenOrCache = holder != null;
5436 }
5437 // 1) Find by position from scrap/hidden list/cache
5438 if (holder == null) {
5439 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
5440 if (holder != null) {
5441 if (!validateViewHolderForOffsetPosition(holder)) {
5442 // recycle holder (and unscrap if relevant) since it can't be used
5443 if (!dryRun) {
5444 // we would like to recycle this but need to make sure it is not used by
5445 // animation logic etc.
5446 holder.addFlags(ViewHolder.FLAG_INVALID);
5447 if (holder.isScrap()) {
5448 removeDetachedView(holder.itemView, false);
5449 holder.unScrap();
5450 } else if (holder.wasReturnedFromScrap()) {
5451 holder.clearReturnedFromScrapFlag();
5452 }
5453 recycleViewHolderInternal(holder);
5454 }
5455 holder = null;
5456 } else {
5457 fromScrapOrHiddenOrCache = true;
5458 }
5459 }
5460 }
5461 if (holder == null) {
5462 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
5463 if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
5464 throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
5465 + "position " + position + "(offset:" + offsetPosition + ")."
5466 + "state:" + mState.getItemCount());
5467 }
5468
5469 final int type = mAdapter.getItemViewType(offsetPosition);
5470 // 2) Find from scrap/cache via stable ids, if exists
5471 if (mAdapter.hasStableIds()) {
5472 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
5473 type, dryRun);
5474 if (holder != null) {
5475 // update position
5476 holder.mPosition = offsetPosition;
5477 fromScrapOrHiddenOrCache = true;
5478 }
5479 }
5480 if (holder == null && mViewCacheExtension != null) {
5481 // We are NOT sending the offsetPosition because LayoutManager does not
5482 // know it.
5483 final View view = mViewCacheExtension
5484 .getViewForPositionAndType(this, position, type);
5485 if (view != null) {
5486 holder = getChildViewHolder(view);
5487 if (holder == null) {
5488 throw new IllegalArgumentException("getViewForPositionAndType returned"
5489 + " a view which does not have a ViewHolder");
5490 } else if (holder.shouldIgnore()) {
5491 throw new IllegalArgumentException("getViewForPositionAndType returned"
5492 + " a view that is ignored. You must call stopIgnoring before"
5493 + " returning this view.");
5494 }
5495 }
5496 }
5497 if (holder == null) { // fallback to pool
5498 if (DEBUG) {
5499 Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
5500 + position + ") fetching from shared pool");
5501 }
5502 holder = getRecycledViewPool().getRecycledView(type);
5503 if (holder != null) {
5504 holder.resetInternal();
5505 if (FORCE_INVALIDATE_DISPLAY_LIST) {
5506 invalidateDisplayListInt(holder);
5507 }
5508 }
5509 }
5510 if (holder == null) {
5511 long start = getNanoTime();
5512 if (deadlineNs != FOREVER_NS
5513 && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
5514 // abort - we have a deadline we can't meet
5515 return null;
5516 }
5517 holder = mAdapter.createViewHolder(RecyclerView.this, type);
5518 if (ALLOW_THREAD_GAP_WORK) {
5519 // only bother finding nested RV if prefetching
5520 RecyclerView innerView = findNestedRecyclerView(holder.itemView);
5521 if (innerView != null) {
5522 holder.mNestedRecyclerView = new WeakReference<>(innerView);
5523 }
5524 }
5525
5526 long end = getNanoTime();
5527 mRecyclerPool.factorInCreateTime(type, end - start);
5528 if (DEBUG) {
5529 Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
5530 }
5531 }
5532 }
5533
5534 // This is very ugly but the only place we can grab this information
5535 // before the View is rebound and returned to the LayoutManager for post layout ops.
5536 // We don't need this in pre-layout since the VH is not updated by the LM.
5537 if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
5538 .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
5539 holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
5540 if (mState.mRunSimpleAnimations) {
5541 int changeFlags = ItemAnimator
5542 .buildAdapterChangeFlagsForAnimations(holder);
5543 changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
5544 final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
5545 holder, changeFlags, holder.getUnmodifiedPayloads());
5546 recordAnimationInfoIfBouncedHiddenView(holder, info);
5547 }
5548 }
5549
5550 boolean bound = false;
5551 if (mState.isPreLayout() && holder.isBound()) {
5552 // do not update unless we absolutely have to.
5553 holder.mPreLayoutPosition = position;
5554 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
5555 if (DEBUG && holder.isRemoved()) {
5556 throw new IllegalStateException("Removed holder should be bound and it should"
5557 + " come here only in pre-layout. Holder: " + holder);
5558 }
5559 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
5560 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
5561 }
5562
5563 final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
5564 final LayoutParams rvLayoutParams;
5565 if (lp == null) {
5566 rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
5567 holder.itemView.setLayoutParams(rvLayoutParams);
5568 } else if (!checkLayoutParams(lp)) {
5569 rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
5570 holder.itemView.setLayoutParams(rvLayoutParams);
5571 } else {
5572 rvLayoutParams = (LayoutParams) lp;
5573 }
5574 rvLayoutParams.mViewHolder = holder;
5575 rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
5576 return holder;
5577 }
5578
5579 private void attachAccessibilityDelegate(View itemView) {
5580 if (isAccessibilityEnabled()) {
5581 if (itemView.getImportantForAccessibility()
5582 == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
5583 itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
5584 }
5585
5586 if (itemView.getAccessibilityDelegate() == null) {
5587 itemView.setAccessibilityDelegate(mAccessibilityDelegate.getItemDelegate());
5588 }
5589 }
5590 }
5591
5592 private void invalidateDisplayListInt(ViewHolder holder) {
5593 if (holder.itemView instanceof ViewGroup) {
5594 invalidateDisplayListInt((ViewGroup) holder.itemView, false);
5595 }
5596 }
5597
5598 private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) {
5599 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
5600 final View view = viewGroup.getChildAt(i);
5601 if (view instanceof ViewGroup) {
5602 invalidateDisplayListInt((ViewGroup) view, true);
5603 }
5604 }
5605 if (!invalidateThis) {
5606 return;
5607 }
5608 // we need to force it to become invisible
5609 if (viewGroup.getVisibility() == View.INVISIBLE) {
5610 viewGroup.setVisibility(View.VISIBLE);
5611 viewGroup.setVisibility(View.INVISIBLE);
5612 } else {
5613 final int visibility = viewGroup.getVisibility();
5614 viewGroup.setVisibility(View.INVISIBLE);
5615 viewGroup.setVisibility(visibility);
5616 }
5617 }
5618
5619 /**
5620 * Recycle a detached view. The specified view will be added to a pool of views
5621 * for later rebinding and reuse.
5622 *
5623 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
5624 * View is scrapped, it will be removed from scrap list.</p>
5625 *
5626 * @param view Removed view for recycling
5627 * @see LayoutManager#removeAndRecycleView(View, Recycler)
5628 */
5629 public void recycleView(View view) {
5630 // This public recycle method tries to make view recycle-able since layout manager
5631 // intended to recycle this view (e.g. even if it is in scrap or change cache)
5632 ViewHolder holder = getChildViewHolderInt(view);
5633 if (holder.isTmpDetached()) {
5634 removeDetachedView(view, false);
5635 }
5636 if (holder.isScrap()) {
5637 holder.unScrap();
5638 } else if (holder.wasReturnedFromScrap()) {
5639 holder.clearReturnedFromScrapFlag();
5640 }
5641 recycleViewHolderInternal(holder);
5642 }
5643
5644 /**
5645 * Internally, use this method instead of {@link #recycleView(android.view.View)} to
5646 * catch potential bugs.
5647 * @param view
5648 */
5649 void recycleViewInternal(View view) {
5650 recycleViewHolderInternal(getChildViewHolderInt(view));
5651 }
5652
5653 void recycleAndClearCachedViews() {
5654 final int count = mCachedViews.size();
5655 for (int i = count - 1; i >= 0; i--) {
5656 recycleCachedViewAt(i);
5657 }
5658 mCachedViews.clear();
5659 if (ALLOW_THREAD_GAP_WORK) {
5660 mPrefetchRegistry.clearPrefetchPositions();
5661 }
5662 }
5663
5664 /**
5665 * Recycles a cached view and removes the view from the list. Views are added to cache
5666 * if and only if they are recyclable, so this method does not check it again.
5667 * <p>
5668 * A small exception to this rule is when the view does not have an animator reference
5669 * but transient state is true (due to animations created outside ItemAnimator). In that
5670 * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is
5671 * still recyclable since Adapter wants to do so.
5672 *
5673 * @param cachedViewIndex The index of the view in cached views list
5674 */
5675 void recycleCachedViewAt(int cachedViewIndex) {
5676 if (DEBUG) {
5677 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
5678 }
5679 ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
5680 if (DEBUG) {
5681 Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
5682 }
5683 addViewHolderToRecycledViewPool(viewHolder, true);
5684 mCachedViews.remove(cachedViewIndex);
5685 }
5686
5687 /**
5688 * internal implementation checks if view is scrapped or attached and throws an exception
5689 * if so.
5690 * Public version un-scraps before calling recycle.
5691 */
5692 void recycleViewHolderInternal(ViewHolder holder) {
5693 if (holder.isScrap() || holder.itemView.getParent() != null) {
5694 throw new IllegalArgumentException(
5695 "Scrapped or attached views may not be recycled. isScrap:"
5696 + holder.isScrap() + " isAttached:"
5697 + (holder.itemView.getParent() != null));
5698 }
5699
5700 if (holder.isTmpDetached()) {
5701 throw new IllegalArgumentException("Tmp detached view should be removed "
5702 + "from RecyclerView before it can be recycled: " + holder);
5703 }
5704
5705 if (holder.shouldIgnore()) {
5706 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
5707 + " should first call stopIgnoringView(view) before calling recycle.");
5708 }
5709 //noinspection unchecked
5710 final boolean transientStatePreventsRecycling = holder
5711 .doesTransientStatePreventRecycling();
5712 final boolean forceRecycle = mAdapter != null
5713 && transientStatePreventsRecycling
5714 && mAdapter.onFailedToRecycleView(holder);
5715 boolean cached = false;
5716 boolean recycled = false;
5717 if (DEBUG && mCachedViews.contains(holder)) {
5718 throw new IllegalArgumentException("cached view received recycle internal? "
5719 + holder);
5720 }
5721 if (forceRecycle || holder.isRecyclable()) {
5722 if (mViewCacheMax > 0
5723 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
5724 | ViewHolder.FLAG_REMOVED
5725 | ViewHolder.FLAG_UPDATE
5726 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
5727 // Retire oldest cached view
5728 int cachedViewSize = mCachedViews.size();
5729 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
5730 recycleCachedViewAt(0);
5731 cachedViewSize--;
5732 }
5733
5734 int targetCacheIndex = cachedViewSize;
5735 if (ALLOW_THREAD_GAP_WORK
5736 && cachedViewSize > 0
5737 && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
5738 // when adding the view, skip past most recently prefetched views
5739 int cacheIndex = cachedViewSize - 1;
5740 while (cacheIndex >= 0) {
5741 int cachedPos = mCachedViews.get(cacheIndex).mPosition;
5742 if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
5743 break;
5744 }
5745 cacheIndex--;
5746 }
5747 targetCacheIndex = cacheIndex + 1;
5748 }
5749 mCachedViews.add(targetCacheIndex, holder);
5750 cached = true;
5751 }
5752 if (!cached) {
5753 addViewHolderToRecycledViewPool(holder, true);
5754 recycled = true;
5755 }
5756 } else {
5757 // NOTE: A view can fail to be recycled when it is scrolled off while an animation
5758 // runs. In this case, the item is eventually recycled by
5759 // ItemAnimatorRestoreListener#onAnimationFinished.
5760
5761 // TODO: consider cancelling an animation when an item is removed scrollBy,
5762 // to return it to the pool faster
5763 if (DEBUG) {
5764 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
5765 + "re-visit here. We are still removing it from animation lists");
5766 }
5767 }
5768 // even if the holder is not removed, we still call this method so that it is removed
5769 // from view holder lists.
5770 mViewInfoStore.removeViewHolder(holder);
5771 if (!cached && !recycled && transientStatePreventsRecycling) {
5772 holder.mOwnerRecyclerView = null;
5773 }
5774 }
5775
5776 /**
5777 * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool.
5778 *
5779 * Pass false to dispatchRecycled for views that have not been bound.
5780 *
5781 * @param holder Holder to be added to the pool.
5782 * @param dispatchRecycled True to dispatch View recycled callbacks.
5783 */
5784 void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
5785 clearNestedRecyclerViewIfNotNested(holder);
5786 holder.itemView.setAccessibilityDelegate(null);
5787 if (dispatchRecycled) {
5788 dispatchViewRecycled(holder);
5789 }
5790 holder.mOwnerRecyclerView = null;
5791 getRecycledViewPool().putRecycledView(holder);
5792 }
5793
5794 /**
5795 * Used as a fast path for unscrapping and recycling a view during a bulk operation.
5796 * The caller must call {@link #clearScrap()} when it's done to update the recycler's
5797 * internal bookkeeping.
5798 */
5799 void quickRecycleScrapView(View view) {
5800 final ViewHolder holder = getChildViewHolderInt(view);
5801 holder.mScrapContainer = null;
5802 holder.mInChangeScrap = false;
5803 holder.clearReturnedFromScrapFlag();
5804 recycleViewHolderInternal(holder);
5805 }
5806
5807 /**
5808 * Mark an attached view as scrap.
5809 *
5810 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
5811 * for rebinding and reuse. Requests for a view for a given position may return a
5812 * reused or rebound scrap view instance.</p>
5813 *
5814 * @param view View to scrap
5815 */
5816 void scrapView(View view) {
5817 final ViewHolder holder = getChildViewHolderInt(view);
5818 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
5819 || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
5820 if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
5821 throw new IllegalArgumentException("Called scrap view with an invalid view."
5822 + " Invalid views cannot be reused from scrap, they should rebound from"
5823 + " recycler pool.");
5824 }
5825 holder.setScrapContainer(this, false);
5826 mAttachedScrap.add(holder);
5827 } else {
5828 if (mChangedScrap == null) {
5829 mChangedScrap = new ArrayList<ViewHolder>();
5830 }
5831 holder.setScrapContainer(this, true);
5832 mChangedScrap.add(holder);
5833 }
5834 }
5835
5836 /**
5837 * Remove a previously scrapped view from the pool of eligible scrap.
5838 *
5839 * <p>This view will no longer be eligible for reuse until re-scrapped or
5840 * until it is explicitly removed and recycled.</p>
5841 */
5842 void unscrapView(ViewHolder holder) {
5843 if (holder.mInChangeScrap) {
5844 mChangedScrap.remove(holder);
5845 } else {
5846 mAttachedScrap.remove(holder);
5847 }
5848 holder.mScrapContainer = null;
5849 holder.mInChangeScrap = false;
5850 holder.clearReturnedFromScrapFlag();
5851 }
5852
5853 int getScrapCount() {
5854 return mAttachedScrap.size();
5855 }
5856
5857 View getScrapViewAt(int index) {
5858 return mAttachedScrap.get(index).itemView;
5859 }
5860
5861 void clearScrap() {
5862 mAttachedScrap.clear();
5863 if (mChangedScrap != null) {
5864 mChangedScrap.clear();
5865 }
5866 }
5867
5868 ViewHolder getChangedScrapViewForPosition(int position) {
5869 // If pre-layout, check the changed scrap for an exact match.
5870 final int changedScrapSize;
5871 if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
5872 return null;
5873 }
5874 // find by position
5875 for (int i = 0; i < changedScrapSize; i++) {
5876 final ViewHolder holder = mChangedScrap.get(i);
5877 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
5878 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
5879 return holder;
5880 }
5881 }
5882 // find by id
5883 if (mAdapter.hasStableIds()) {
5884 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
5885 if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
5886 final long id = mAdapter.getItemId(offsetPosition);
5887 for (int i = 0; i < changedScrapSize; i++) {
5888 final ViewHolder holder = mChangedScrap.get(i);
5889 if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
5890 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
5891 return holder;
5892 }
5893 }
5894 }
5895 }
5896 return null;
5897 }
5898
5899 /**
5900 * Returns a view for the position either from attach scrap, hidden children, or cache.
5901 *
5902 * @param position Item position
5903 * @param dryRun Does a dry run, finds the ViewHolder but does not remove
5904 * @return a ViewHolder that can be re-used for this position.
5905 */
5906 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
5907 final int scrapCount = mAttachedScrap.size();
5908
5909 // Try first for an exact, non-invalid match from scrap.
5910 for (int i = 0; i < scrapCount; i++) {
5911 final ViewHolder holder = mAttachedScrap.get(i);
5912 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
5913 && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
5914 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
5915 return holder;
5916 }
5917 }
5918
5919 if (!dryRun) {
5920 View view = mChildHelper.findHiddenNonRemovedView(position);
5921 if (view != null) {
5922 // This View is good to be used. We just need to unhide, detach and move to the
5923 // scrap list.
5924 final ViewHolder vh = getChildViewHolderInt(view);
5925 mChildHelper.unhide(view);
5926 int layoutIndex = mChildHelper.indexOfChild(view);
5927 if (layoutIndex == RecyclerView.NO_POSITION) {
5928 throw new IllegalStateException("layout index should not be -1 after "
5929 + "unhiding a view:" + vh);
5930 }
5931 mChildHelper.detachViewFromParent(layoutIndex);
5932 scrapView(view);
5933 vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
5934 | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
5935 return vh;
5936 }
5937 }
5938
5939 // Search in our first-level recycled view cache.
5940 final int cacheSize = mCachedViews.size();
5941 for (int i = 0; i < cacheSize; i++) {
5942 final ViewHolder holder = mCachedViews.get(i);
5943 // invalid view holders may be in cache if adapter has stable ids as they can be
5944 // retrieved via getScrapOrCachedViewForId
5945 if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
5946 if (!dryRun) {
5947 mCachedViews.remove(i);
5948 }
5949 if (DEBUG) {
5950 Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
5951 + ") found match in cache: " + holder);
5952 }
5953 return holder;
5954 }
5955 }
5956 return null;
5957 }
5958
5959 ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
5960 // Look in our attached views first
5961 final int count = mAttachedScrap.size();
5962 for (int i = count - 1; i >= 0; i--) {
5963 final ViewHolder holder = mAttachedScrap.get(i);
5964 if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
5965 if (type == holder.getItemViewType()) {
5966 holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
5967 if (holder.isRemoved()) {
5968 // this might be valid in two cases:
5969 // > item is removed but we are in pre-layout pass
5970 // >> do nothing. return as is. make sure we don't rebind
5971 // > item is removed then added to another position and we are in
5972 // post layout.
5973 // >> remove removed and invalid flags, add update flag to rebind
5974 // because item was invisible to us and we don't know what happened in
5975 // between.
5976 if (!mState.isPreLayout()) {
5977 holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
5978 | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
5979 }
5980 }
5981 return holder;
5982 } else if (!dryRun) {
5983 // if we are running animations, it is actually better to keep it in scrap
5984 // but this would force layout manager to lay it out which would be bad.
5985 // Recycle this scrap. Type mismatch.
5986 mAttachedScrap.remove(i);
5987 removeDetachedView(holder.itemView, false);
5988 quickRecycleScrapView(holder.itemView);
5989 }
5990 }
5991 }
5992
5993 // Search the first-level cache
5994 final int cacheSize = mCachedViews.size();
5995 for (int i = cacheSize - 1; i >= 0; i--) {
5996 final ViewHolder holder = mCachedViews.get(i);
5997 if (holder.getItemId() == id) {
5998 if (type == holder.getItemViewType()) {
5999 if (!dryRun) {
6000 mCachedViews.remove(i);
6001 }
6002 return holder;
6003 } else if (!dryRun) {
6004 recycleCachedViewAt(i);
6005 return null;
6006 }
6007 }
6008 }
6009 return null;
6010 }
6011
6012 void dispatchViewRecycled(ViewHolder holder) {
6013 if (mRecyclerListener != null) {
6014 mRecyclerListener.onViewRecycled(holder);
6015 }
6016 if (mAdapter != null) {
6017 mAdapter.onViewRecycled(holder);
6018 }
6019 if (mState != null) {
6020 mViewInfoStore.removeViewHolder(holder);
6021 }
6022 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
6023 }
6024
6025 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
6026 boolean compatibleWithPrevious) {
6027 clear();
6028 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
6029 }
6030
6031 void offsetPositionRecordsForMove(int from, int to) {
6032 final int start, end, inBetweenOffset;
6033 if (from < to) {
6034 start = from;
6035 end = to;
6036 inBetweenOffset = -1;
6037 } else {
6038 start = to;
6039 end = from;
6040 inBetweenOffset = 1;
6041 }
6042 final int cachedCount = mCachedViews.size();
6043 for (int i = 0; i < cachedCount; i++) {
6044 final ViewHolder holder = mCachedViews.get(i);
6045 if (holder == null || holder.mPosition < start || holder.mPosition > end) {
6046 continue;
6047 }
6048 if (holder.mPosition == from) {
6049 holder.offsetPosition(to - from, false);
6050 } else {
6051 holder.offsetPosition(inBetweenOffset, false);
6052 }
6053 if (DEBUG) {
6054 Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder "
6055 + holder);
6056 }
6057 }
6058 }
6059
6060 void offsetPositionRecordsForInsert(int insertedAt, int count) {
6061 final int cachedCount = mCachedViews.size();
6062 for (int i = 0; i < cachedCount; i++) {
6063 final ViewHolder holder = mCachedViews.get(i);
6064 if (holder != null && holder.mPosition >= insertedAt) {
6065 if (DEBUG) {
6066 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder "
6067 + holder + " now at position " + (holder.mPosition + count));
6068 }
6069 holder.offsetPosition(count, true);
6070 }
6071 }
6072 }
6073
6074 /**
6075 * @param removedFrom Remove start index
6076 * @param count Remove count
6077 * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if
6078 * false, they'll be applied before the second layout pass
6079 */
6080 void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
6081 final int removedEnd = removedFrom + count;
6082 final int cachedCount = mCachedViews.size();
6083 for (int i = cachedCount - 1; i >= 0; i--) {
6084 final ViewHolder holder = mCachedViews.get(i);
6085 if (holder != null) {
6086 if (holder.mPosition >= removedEnd) {
6087 if (DEBUG) {
6088 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i
6089 + " holder " + holder + " now at position "
6090 + (holder.mPosition - count));
6091 }
6092 holder.offsetPosition(-count, applyToPreLayout);
6093 } else if (holder.mPosition >= removedFrom) {
6094 // Item for this view was removed. Dump it from the cache.
6095 holder.addFlags(ViewHolder.FLAG_REMOVED);
6096 recycleCachedViewAt(i);
6097 }
6098 }
6099 }
6100 }
6101
6102 void setViewCacheExtension(ViewCacheExtension extension) {
6103 mViewCacheExtension = extension;
6104 }
6105
6106 void setRecycledViewPool(RecycledViewPool pool) {
6107 if (mRecyclerPool != null) {
6108 mRecyclerPool.detach();
6109 }
6110 mRecyclerPool = pool;
6111 if (pool != null) {
6112 mRecyclerPool.attach(getAdapter());
6113 }
6114 }
6115
6116 RecycledViewPool getRecycledViewPool() {
6117 if (mRecyclerPool == null) {
6118 mRecyclerPool = new RecycledViewPool();
6119 }
6120 return mRecyclerPool;
6121 }
6122
6123 void viewRangeUpdate(int positionStart, int itemCount) {
6124 final int positionEnd = positionStart + itemCount;
6125 final int cachedCount = mCachedViews.size();
6126 for (int i = cachedCount - 1; i >= 0; i--) {
6127 final ViewHolder holder = mCachedViews.get(i);
6128 if (holder == null) {
6129 continue;
6130 }
6131
6132 final int pos = holder.getLayoutPosition();
6133 if (pos >= positionStart && pos < positionEnd) {
6134 holder.addFlags(ViewHolder.FLAG_UPDATE);
6135 recycleCachedViewAt(i);
6136 // cached views should not be flagged as changed because this will cause them
6137 // to animate when they are returned from cache.
6138 }
6139 }
6140 }
6141
6142 void setAdapterPositionsAsUnknown() {
6143 final int cachedCount = mCachedViews.size();
6144 for (int i = 0; i < cachedCount; i++) {
6145 final ViewHolder holder = mCachedViews.get(i);
6146 if (holder != null) {
6147 holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
6148 }
6149 }
6150 }
6151
6152 void markKnownViewsInvalid() {
6153 if (mAdapter != null && mAdapter.hasStableIds()) {
6154 final int cachedCount = mCachedViews.size();
6155 for (int i = 0; i < cachedCount; i++) {
6156 final ViewHolder holder = mCachedViews.get(i);
6157 if (holder != null) {
6158 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
6159 holder.addChangePayload(null);
6160 }
6161 }
6162 } else {
6163 // we cannot re-use cached views in this case. Recycle them all
6164 recycleAndClearCachedViews();
6165 }
6166 }
6167
6168 void clearOldPositions() {
6169 final int cachedCount = mCachedViews.size();
6170 for (int i = 0; i < cachedCount; i++) {
6171 final ViewHolder holder = mCachedViews.get(i);
6172 holder.clearOldPosition();
6173 }
6174 final int scrapCount = mAttachedScrap.size();
6175 for (int i = 0; i < scrapCount; i++) {
6176 mAttachedScrap.get(i).clearOldPosition();
6177 }
6178 if (mChangedScrap != null) {
6179 final int changedScrapCount = mChangedScrap.size();
6180 for (int i = 0; i < changedScrapCount; i++) {
6181 mChangedScrap.get(i).clearOldPosition();
6182 }
6183 }
6184 }
6185
6186 void markItemDecorInsetsDirty() {
6187 final int cachedCount = mCachedViews.size();
6188 for (int i = 0; i < cachedCount; i++) {
6189 final ViewHolder holder = mCachedViews.get(i);
6190 LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
6191 if (layoutParams != null) {
6192 layoutParams.mInsetsDirty = true;
6193 }
6194 }
6195 }
6196 }
6197
6198 /**
6199 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
6200 * be controlled by the developer.
6201 * <p>
6202 * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
6203 * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
6204 * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
6205 * {@link RecycledViewPool}.
6206 * <p>
6207 * Note that, Recycler never sends Views to this method to be cached. It is developers
6208 * responsibility to decide whether they want to keep their Views in this custom cache or let
6209 * the default recycling policy handle it.
6210 */
6211 public abstract static class ViewCacheExtension {
6212
6213 /**
6214 * Returns a View that can be binded to the given Adapter position.
6215 * <p>
6216 * This method should <b>not</b> create a new View. Instead, it is expected to return
6217 * an already created View that can be re-used for the given type and position.
6218 * If the View is marked as ignored, it should first call
6219 * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
6220 * <p>
6221 * RecyclerView will re-bind the returned View to the position if necessary.
6222 *
6223 * @param recycler The Recycler that can be used to bind the View
6224 * @param position The adapter position
6225 * @param type The type of the View, defined by adapter
6226 * @return A View that is bound to the given position or NULL if there is no View to re-use
6227 * @see LayoutManager#ignoreView(View)
6228 */
6229 public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
6230 }
6231
6232 /**
6233 * Base class for an Adapter
6234 *
6235 * <p>Adapters provide a binding from an app-specific data set to views that are displayed
6236 * within a {@link RecyclerView}.</p>
6237 *
6238 * @param <VH> A class that extends ViewHolder that will be used by the adapter.
6239 */
6240 public abstract static class Adapter<VH extends ViewHolder> {
6241 private final AdapterDataObservable mObservable = new AdapterDataObservable();
6242 private boolean mHasStableIds = false;
6243
6244 /**
6245 * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
6246 * an item.
6247 * <p>
6248 * This new ViewHolder should be constructed with a new View that can represent the items
6249 * of the given type. You can either create a new View manually or inflate it from an XML
6250 * layout file.
6251 * <p>
6252 * The new ViewHolder will be used to display items of the adapter using
6253 * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
6254 * different items in the data set, it is a good idea to cache references to sub views of
6255 * the View to avoid unnecessary {@link View#findViewById(int)} calls.
6256 *
6257 * @param parent The ViewGroup into which the new View will be added after it is bound to
6258 * an adapter position.
6259 * @param viewType The view type of the new View.
6260 *
6261 * @return A new ViewHolder that holds a View of the given view type.
6262 * @see #getItemViewType(int)
6263 * @see #onBindViewHolder(ViewHolder, int)
6264 */
6265 public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
6266
6267 /**
6268 * Called by RecyclerView to display the data at the specified position. This method should
6269 * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
6270 * position.
6271 * <p>
6272 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
6273 * again if the position of the item changes in the data set unless the item itself is
6274 * invalidated or the new position cannot be determined. For this reason, you should only
6275 * use the <code>position</code> parameter while acquiring the related data item inside
6276 * this method and should not keep a copy of it. If you need the position of an item later
6277 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
6278 * have the updated adapter position.
6279 *
6280 * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
6281 * handle efficient partial bind.
6282 *
6283 * @param holder The ViewHolder which should be updated to represent the contents of the
6284 * item at the given position in the data set.
6285 * @param position The position of the item within the adapter's data set.
6286 */
6287 public abstract void onBindViewHolder(VH holder, int position);
6288
6289 /**
6290 * Called by RecyclerView to display the data at the specified position. This method
6291 * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
6292 * the given position.
6293 * <p>
6294 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
6295 * again if the position of the item changes in the data set unless the item itself is
6296 * invalidated or the new position cannot be determined. For this reason, you should only
6297 * use the <code>position</code> parameter while acquiring the related data item inside
6298 * this method and should not keep a copy of it. If you need the position of an item later
6299 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
6300 * have the updated adapter position.
6301 * <p>
6302 * Partial bind vs full bind:
6303 * <p>
6304 * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
6305 * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty,
6306 * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
6307 * update using the payload info. If the payload is empty, Adapter must run a full bind.
6308 * Adapter should not assume that the payload passed in notify methods will be received by
6309 * onBindViewHolder(). For example when the view is not attached to the screen, the
6310 * payload in notifyItemChange() will be simply dropped.
6311 *
6312 * @param holder The ViewHolder which should be updated to represent the contents of the
6313 * item at the given position in the data set.
6314 * @param position The position of the item within the adapter's data set.
6315 * @param payloads A non-null list of merged payloads. Can be empty list if requires full
6316 * update.
6317 */
6318 public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
6319 onBindViewHolder(holder, position);
6320 }
6321
6322 /**
6323 * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
6324 * {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
6325 *
6326 * @see #onCreateViewHolder(ViewGroup, int)
6327 */
6328 public final VH createViewHolder(ViewGroup parent, int viewType) {
6329 Trace.beginSection(TRACE_CREATE_VIEW_TAG);
6330 final VH holder = onCreateViewHolder(parent, viewType);
6331 holder.mItemViewType = viewType;
6332 Trace.endSection();
6333 return holder;
6334 }
6335
6336 /**
6337 * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the
6338 * {@link ViewHolder} contents with the item at the given position and also sets up some
6339 * private fields to be used by RecyclerView.
6340 *
6341 * @see #onBindViewHolder(ViewHolder, int)
6342 */
6343 public final void bindViewHolder(VH holder, int position) {
6344 holder.mPosition = position;
6345 if (hasStableIds()) {
6346 holder.mItemId = getItemId(position);
6347 }
6348 holder.setFlags(ViewHolder.FLAG_BOUND,
6349 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
6350 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
6351 Trace.beginSection(TRACE_BIND_VIEW_TAG);
6352 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
6353 holder.clearPayload();
6354 final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
6355 if (layoutParams instanceof RecyclerView.LayoutParams) {
6356 ((LayoutParams) layoutParams).mInsetsDirty = true;
6357 }
6358 Trace.endSection();
6359 }
6360
6361 /**
6362 * Return the view type of the item at <code>position</code> for the purposes
6363 * of view recycling.
6364 *
6365 * <p>The default implementation of this method returns 0, making the assumption of
6366 * a single view type for the adapter. Unlike ListView adapters, types need not
6367 * be contiguous. Consider using id resources to uniquely identify item view types.
6368 *
6369 * @param position position to query
6370 * @return integer value identifying the type of the view needed to represent the item at
6371 * <code>position</code>. Type codes need not be contiguous.
6372 */
6373 public int getItemViewType(int position) {
6374 return 0;
6375 }
6376
6377 /**
6378 * Indicates whether each item in the data set can be represented with a unique identifier
6379 * of type {@link java.lang.Long}.
6380 *
6381 * @param hasStableIds Whether items in data set have unique identifiers or not.
6382 * @see #hasStableIds()
6383 * @see #getItemId(int)
6384 */
6385 public void setHasStableIds(boolean hasStableIds) {
6386 if (hasObservers()) {
6387 throw new IllegalStateException("Cannot change whether this adapter has "
6388 + "stable IDs while the adapter has registered observers.");
6389 }
6390 mHasStableIds = hasStableIds;
6391 }
6392
6393 /**
6394 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
6395 * would return false this method should return {@link #NO_ID}. The default implementation
6396 * of this method returns {@link #NO_ID}.
6397 *
6398 * @param position Adapter position to query
6399 * @return the stable ID of the item at position
6400 */
6401 public long getItemId(int position) {
6402 return NO_ID;
6403 }
6404
6405 /**
6406 * Returns the total number of items in the data set held by the adapter.
6407 *
6408 * @return The total number of items in this adapter.
6409 */
6410 public abstract int getItemCount();
6411
6412 /**
6413 * Returns true if this adapter publishes a unique <code>long</code> value that can
6414 * act as a key for the item at a given position in the data set. If that item is relocated
6415 * in the data set, the ID returned for that item should be the same.
6416 *
6417 * @return true if this adapter's items have stable IDs
6418 */
6419 public final boolean hasStableIds() {
6420 return mHasStableIds;
6421 }
6422
6423 /**
6424 * Called when a view created by this adapter has been recycled.
6425 *
6426 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
6427 * needs to be attached to its parent {@link RecyclerView}. This can be because it has
6428 * fallen out of visibility or a set of cached views represented by views still
6429 * attached to the parent RecyclerView. If an item view has large or expensive data
6430 * bound to it such as large bitmaps, this may be a good place to release those
6431 * resources.</p>
6432 * <p>
6433 * RecyclerView calls this method right before clearing ViewHolder's internal data and
6434 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
6435 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
6436 * its adapter position.
6437 *
6438 * @param holder The ViewHolder for the view being recycled
6439 */
6440 public void onViewRecycled(VH holder) {
6441 }
6442
6443 /**
6444 * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
6445 * due to its transient state. Upon receiving this callback, Adapter can clear the
6446 * animation(s) that effect the View's transient state and return <code>true</code> so that
6447 * the View can be recycled. Keep in mind that the View in question is already removed from
6448 * the RecyclerView.
6449 * <p>
6450 * In some cases, it is acceptable to recycle a View although it has transient state. Most
6451 * of the time, this is a case where the transient state will be cleared in
6452 * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position.
6453 * For this reason, RecyclerView leaves the decision to the Adapter and uses the return
6454 * value of this method to decide whether the View should be recycled or not.
6455 * <p>
6456 * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you
6457 * should never receive this callback because RecyclerView keeps those Views as children
6458 * until their animations are complete. This callback is useful when children of the item
6459 * views create animations which may not be easy to implement using an {@link ItemAnimator}.
6460 * <p>
6461 * You should <em>never</em> fix this issue by calling
6462 * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called
6463 * <code>holder.itemView.setHasTransientState(true);</code>. Each
6464 * <code>View.setHasTransientState(true)</code> call must be matched by a
6465 * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View
6466 * may become inconsistent. You should always prefer to end or cancel animations that are
6467 * triggering the transient state instead of handling it manually.
6468 *
6469 * @param holder The ViewHolder containing the View that could not be recycled due to its
6470 * transient state.
6471 * @return True if the View should be recycled, false otherwise. Note that if this method
6472 * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of
6473 * the View and recycle it regardless. If this method returns <code>false</code>,
6474 * RecyclerView will check the View's transient state again before giving a final decision.
6475 * Default implementation returns false.
6476 */
6477 public boolean onFailedToRecycleView(VH holder) {
6478 return false;
6479 }
6480
6481 /**
6482 * Called when a view created by this adapter has been attached to a window.
6483 *
6484 * <p>This can be used as a reasonable signal that the view is about to be seen
6485 * by the user. If the adapter previously freed any resources in
6486 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
6487 * those resources should be restored here.</p>
6488 *
6489 * @param holder Holder of the view being attached
6490 */
6491 public void onViewAttachedToWindow(VH holder) {
6492 }
6493
6494 /**
6495 * Called when a view created by this adapter has been detached from its window.
6496 *
6497 * <p>Becoming detached from the window is not necessarily a permanent condition;
6498 * the consumer of an Adapter's views may choose to cache views offscreen while they
6499 * are not visible, attaching and detaching them as appropriate.</p>
6500 *
6501 * @param holder Holder of the view being detached
6502 */
6503 public void onViewDetachedFromWindow(VH holder) {
6504 }
6505
6506 /**
6507 * Returns true if one or more observers are attached to this adapter.
6508 *
6509 * @return true if this adapter has observers
6510 */
6511 public final boolean hasObservers() {
6512 return mObservable.hasObservers();
6513 }
6514
6515 /**
6516 * Register a new observer to listen for data changes.
6517 *
6518 * <p>The adapter may publish a variety of events describing specific changes.
6519 * Not all adapters may support all change types and some may fall back to a generic
6520 * {@link com.android.internal.widget.RecyclerView.AdapterDataObserver#onChanged()
6521 * "something changed"} event if more specific data is not available.</p>
6522 *
6523 * <p>Components registering observers with an adapter are responsible for
6524 * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
6525 * unregistering} those observers when finished.</p>
6526 *
6527 * @param observer Observer to register
6528 *
6529 * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
6530 */
6531 public void registerAdapterDataObserver(AdapterDataObserver observer) {
6532 mObservable.registerObserver(observer);
6533 }
6534
6535 /**
6536 * Unregister an observer currently listening for data changes.
6537 *
6538 * <p>The unregistered observer will no longer receive events about changes
6539 * to the adapter.</p>
6540 *
6541 * @param observer Observer to unregister
6542 *
6543 * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
6544 */
6545 public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
6546 mObservable.unregisterObserver(observer);
6547 }
6548
6549 /**
6550 * Called by RecyclerView when it starts observing this Adapter.
6551 * <p>
6552 * Keep in mind that same adapter may be observed by multiple RecyclerViews.
6553 *
6554 * @param recyclerView The RecyclerView instance which started observing this adapter.
6555 * @see #onDetachedFromRecyclerView(RecyclerView)
6556 */
6557 public void onAttachedToRecyclerView(RecyclerView recyclerView) {
6558 }
6559
6560 /**
6561 * Called by RecyclerView when it stops observing this Adapter.
6562 *
6563 * @param recyclerView The RecyclerView instance which stopped observing this adapter.
6564 * @see #onAttachedToRecyclerView(RecyclerView)
6565 */
6566 public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
6567 }
6568
6569 /**
6570 * Notify any registered observers that the data set has changed.
6571 *
6572 * <p>There are two different classes of data change events, item changes and structural
6573 * changes. Item changes are when a single item has its data updated but no positional
6574 * changes have occurred. Structural changes are when items are inserted, removed or moved
6575 * within the data set.</p>
6576 *
6577 * <p>This event does not specify what about the data set has changed, forcing
6578 * any observers to assume that all existing items and structure may no longer be valid.
6579 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
6580 *
6581 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
6582 * for adapters that report that they have {@link #hasStableIds() stable IDs} when
6583 * this method is used. This can help for the purposes of animation and visual
6584 * object persistence but individual item views will still need to be rebound
6585 * and relaid out.</p>
6586 *
6587 * <p>If you are writing an adapter it will always be more efficient to use the more
6588 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
6589 * as a last resort.</p>
6590 *
6591 * @see #notifyItemChanged(int)
6592 * @see #notifyItemInserted(int)
6593 * @see #notifyItemRemoved(int)
6594 * @see #notifyItemRangeChanged(int, int)
6595 * @see #notifyItemRangeInserted(int, int)
6596 * @see #notifyItemRangeRemoved(int, int)
6597 */
6598 public final void notifyDataSetChanged() {
6599 mObservable.notifyChanged();
6600 }
6601
6602 /**
6603 * Notify any registered observers that the item at <code>position</code> has changed.
6604 * Equivalent to calling <code>notifyItemChanged(position, null);</code>.
6605 *
6606 * <p>This is an item change event, not a structural change event. It indicates that any
6607 * reflection of the data at <code>position</code> is out of date and should be updated.
6608 * The item at <code>position</code> retains the same identity.</p>
6609 *
6610 * @param position Position of the item that has changed
6611 *
6612 * @see #notifyItemRangeChanged(int, int)
6613 */
6614 public final void notifyItemChanged(int position) {
6615 mObservable.notifyItemRangeChanged(position, 1);
6616 }
6617
6618 /**
6619 * Notify any registered observers that the item at <code>position</code> has changed with
6620 * an optional payload object.
6621 *
6622 * <p>This is an item change event, not a structural change event. It indicates that any
6623 * reflection of the data at <code>position</code> is out of date and should be updated.
6624 * The item at <code>position</code> retains the same identity.
6625 * </p>
6626 *
6627 * <p>
6628 * Client can optionally pass a payload for partial change. These payloads will be merged
6629 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
6630 * item is already represented by a ViewHolder and it will be rebound to the same
6631 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
6632 * payloads on that item and prevent future payload until
6633 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
6634 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
6635 * attached, the payload will be simply dropped.
6636 *
6637 * @param position Position of the item that has changed
6638 * @param payload Optional parameter, use null to identify a "full" update
6639 *
6640 * @see #notifyItemRangeChanged(int, int)
6641 */
6642 public final void notifyItemChanged(int position, Object payload) {
6643 mObservable.notifyItemRangeChanged(position, 1, payload);
6644 }
6645
6646 /**
6647 * Notify any registered observers that the <code>itemCount</code> items starting at
6648 * position <code>positionStart</code> have changed.
6649 * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>.
6650 *
6651 * <p>This is an item change event, not a structural change event. It indicates that
6652 * any reflection of the data in the given position range is out of date and should
6653 * be updated. The items in the given range retain the same identity.</p>
6654 *
6655 * @param positionStart Position of the first item that has changed
6656 * @param itemCount Number of items that have changed
6657 *
6658 * @see #notifyItemChanged(int)
6659 */
6660 public final void notifyItemRangeChanged(int positionStart, int itemCount) {
6661 mObservable.notifyItemRangeChanged(positionStart, itemCount);
6662 }
6663
6664 /**
6665 * Notify any registered observers that the <code>itemCount</code> items starting at
6666 * position <code>positionStart</code> have changed. An optional payload can be
6667 * passed to each changed item.
6668 *
6669 * <p>This is an item change event, not a structural change event. It indicates that any
6670 * reflection of the data in the given position range is out of date and should be updated.
6671 * The items in the given range retain the same identity.
6672 * </p>
6673 *
6674 * <p>
6675 * Client can optionally pass a payload for partial change. These payloads will be merged
6676 * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
6677 * item is already represented by a ViewHolder and it will be rebound to the same
6678 * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
6679 * payloads on that item and prevent future payload until
6680 * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
6681 * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
6682 * attached, the payload will be simply dropped.
6683 *
6684 * @param positionStart Position of the first item that has changed
6685 * @param itemCount Number of items that have changed
6686 * @param payload Optional parameter, use null to identify a "full" update
6687 *
6688 * @see #notifyItemChanged(int)
6689 */
6690 public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
6691 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
6692 }
6693
6694 /**
6695 * Notify any registered observers that the item reflected at <code>position</code>
6696 * has been newly inserted. The item previously at <code>position</code> is now at
6697 * position <code>position + 1</code>.
6698 *
6699 * <p>This is a structural change event. Representations of other existing items in the
6700 * data set are still considered up to date and will not be rebound, though their
6701 * positions may be altered.</p>
6702 *
6703 * @param position Position of the newly inserted item in the data set
6704 *
6705 * @see #notifyItemRangeInserted(int, int)
6706 */
6707 public final void notifyItemInserted(int position) {
6708 mObservable.notifyItemRangeInserted(position, 1);
6709 }
6710
6711 /**
6712 * Notify any registered observers that the item reflected at <code>fromPosition</code>
6713 * has been moved to <code>toPosition</code>.
6714 *
6715 * <p>This is a structural change event. Representations of other existing items in the
6716 * data set are still considered up to date and will not be rebound, though their
6717 * positions may be altered.</p>
6718 *
6719 * @param fromPosition Previous position of the item.
6720 * @param toPosition New position of the item.
6721 */
6722 public final void notifyItemMoved(int fromPosition, int toPosition) {
6723 mObservable.notifyItemMoved(fromPosition, toPosition);
6724 }
6725
6726 /**
6727 * Notify any registered observers that the currently reflected <code>itemCount</code>
6728 * items starting at <code>positionStart</code> have been newly inserted. The items
6729 * previously located at <code>positionStart</code> and beyond can now be found starting
6730 * at position <code>positionStart + itemCount</code>.
6731 *
6732 * <p>This is a structural change event. Representations of other existing items in the
6733 * data set are still considered up to date and will not be rebound, though their positions
6734 * may be altered.</p>
6735 *
6736 * @param positionStart Position of the first item that was inserted
6737 * @param itemCount Number of items inserted
6738 *
6739 * @see #notifyItemInserted(int)
6740 */
6741 public final void notifyItemRangeInserted(int positionStart, int itemCount) {
6742 mObservable.notifyItemRangeInserted(positionStart, itemCount);
6743 }
6744
6745 /**
6746 * Notify any registered observers that the item previously located at <code>position</code>
6747 * has been removed from the data set. The items previously located at and after
6748 * <code>position</code> may now be found at <code>oldPosition - 1</code>.
6749 *
6750 * <p>This is a structural change event. Representations of other existing items in the
6751 * data set are still considered up to date and will not be rebound, though their positions
6752 * may be altered.</p>
6753 *
6754 * @param position Position of the item that has now been removed
6755 *
6756 * @see #notifyItemRangeRemoved(int, int)
6757 */
6758 public final void notifyItemRemoved(int position) {
6759 mObservable.notifyItemRangeRemoved(position, 1);
6760 }
6761
6762 /**
6763 * Notify any registered observers that the <code>itemCount</code> items previously
6764 * located at <code>positionStart</code> have been removed from the data set. The items
6765 * previously located at and after <code>positionStart + itemCount</code> may now be found
6766 * at <code>oldPosition - itemCount</code>.
6767 *
6768 * <p>This is a structural change event. Representations of other existing items in the data
6769 * set are still considered up to date and will not be rebound, though their positions
6770 * may be altered.</p>
6771 *
6772 * @param positionStart Previous position of the first item that was removed
6773 * @param itemCount Number of items removed from the data set
6774 */
6775 public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
6776 mObservable.notifyItemRangeRemoved(positionStart, itemCount);
6777 }
6778 }
6779
6780 void dispatchChildDetached(View child) {
6781 final ViewHolder viewHolder = getChildViewHolderInt(child);
6782 onChildDetachedFromWindow(child);
6783 if (mAdapter != null && viewHolder != null) {
6784 mAdapter.onViewDetachedFromWindow(viewHolder);
6785 }
6786 if (mOnChildAttachStateListeners != null) {
6787 final int cnt = mOnChildAttachStateListeners.size();
6788 for (int i = cnt - 1; i >= 0; i--) {
6789 mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
6790 }
6791 }
6792 }
6793
6794 void dispatchChildAttached(View child) {
6795 final ViewHolder viewHolder = getChildViewHolderInt(child);
6796 onChildAttachedToWindow(child);
6797 if (mAdapter != null && viewHolder != null) {
6798 mAdapter.onViewAttachedToWindow(viewHolder);
6799 }
6800 if (mOnChildAttachStateListeners != null) {
6801 final int cnt = mOnChildAttachStateListeners.size();
6802 for (int i = cnt - 1; i >= 0; i--) {
6803 mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
6804 }
6805 }
6806 }
6807
6808 /**
6809 * A <code>LayoutManager</code> is responsible for measuring and positioning item views
6810 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
6811 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
6812 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
6813 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
6814 * layout managers are provided for general use.
6815 * <p/>
6816 * If the LayoutManager specifies a default constructor or one with the signature
6817 * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will
6818 * instantiate and set the LayoutManager when being inflated. Most used properties can
6819 * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case
6820 * a LayoutManager specifies both constructors, the non-default constructor will take
6821 * precedence.
6822 *
6823 */
6824 public abstract static class LayoutManager {
6825 ChildHelper mChildHelper;
6826 RecyclerView mRecyclerView;
6827
6828 @Nullable
6829 SmoothScroller mSmoothScroller;
6830
6831 boolean mRequestedSimpleAnimations = false;
6832
6833 boolean mIsAttachedToWindow = false;
6834
6835 boolean mAutoMeasure = false;
6836
6837 /**
6838 * LayoutManager has its own more strict measurement cache to avoid re-measuring a child
6839 * if the space that will be given to it is already larger than what it has measured before.
6840 */
6841 private boolean mMeasurementCacheEnabled = true;
6842
6843 private boolean mItemPrefetchEnabled = true;
6844
6845 /**
6846 * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
6847 * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
6848 * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
6849 *
6850 * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
6851 * will be reset upon layout to prevent initial prefetches (often large, since they're
6852 * proportional to expected child count) from expanding cache permanently.
6853 */
6854 int mPrefetchMaxCountObserved;
6855
6856 /**
6857 * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset.
6858 */
6859 boolean mPrefetchMaxObservedInInitialPrefetch;
6860
6861 /**
6862 * These measure specs might be the measure specs that were passed into RecyclerView's
6863 * onMeasure method OR fake measure specs created by the RecyclerView.
6864 * For example, when a layout is run, RecyclerView always sets these specs to be
6865 * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
6866 * <p>
6867 * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the
6868 * API level and sets the size to 0 pre-M to avoid any issue that might be caused by
6869 * corrupt values. Older platforms have no responsibility to provide a size if they set
6870 * mode to unspecified.
6871 */
6872 private int mWidthMode, mHeightMode;
6873 private int mWidth, mHeight;
6874
6875
6876 /**
6877 * Interface for LayoutManagers to request items to be prefetched, based on position, with
6878 * specified distance from viewport, which indicates priority.
6879 *
6880 * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
6881 * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
6882 */
6883 public interface LayoutPrefetchRegistry {
6884 /**
6885 * Requests an an item to be prefetched, based on position, with a specified distance,
6886 * indicating priority.
6887 *
6888 * @param layoutPosition Position of the item to prefetch.
6889 * @param pixelDistance Distance from the current viewport to the bounds of the item,
6890 * must be non-negative.
6891 */
6892 void addPosition(int layoutPosition, int pixelDistance);
6893 }
6894
6895 void setRecyclerView(RecyclerView recyclerView) {
6896 if (recyclerView == null) {
6897 mRecyclerView = null;
6898 mChildHelper = null;
6899 mWidth = 0;
6900 mHeight = 0;
6901 } else {
6902 mRecyclerView = recyclerView;
6903 mChildHelper = recyclerView.mChildHelper;
6904 mWidth = recyclerView.getWidth();
6905 mHeight = recyclerView.getHeight();
6906 }
6907 mWidthMode = MeasureSpec.EXACTLY;
6908 mHeightMode = MeasureSpec.EXACTLY;
6909 }
6910
6911 void setMeasureSpecs(int wSpec, int hSpec) {
6912 mWidth = MeasureSpec.getSize(wSpec);
6913 mWidthMode = MeasureSpec.getMode(wSpec);
6914 if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
6915 mWidth = 0;
6916 }
6917
6918 mHeight = MeasureSpec.getSize(hSpec);
6919 mHeightMode = MeasureSpec.getMode(hSpec);
6920 if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
6921 mHeight = 0;
6922 }
6923 }
6924
6925 /**
6926 * Called after a layout is calculated during a measure pass when using auto-measure.
6927 * <p>
6928 * It simply traverses all children to calculate a bounding box then calls
6929 * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method
6930 * if they need to handle the bounding box differently.
6931 * <p>
6932 * For example, GridLayoutManager override that method to ensure that even if a column is
6933 * empty, the GridLayoutManager still measures wide enough to include it.
6934 *
6935 * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure
6936 * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure
6937 */
6938 void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
6939 final int count = getChildCount();
6940 if (count == 0) {
6941 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
6942 return;
6943 }
6944 int minX = Integer.MAX_VALUE;
6945 int minY = Integer.MAX_VALUE;
6946 int maxX = Integer.MIN_VALUE;
6947 int maxY = Integer.MIN_VALUE;
6948
6949 for (int i = 0; i < count; i++) {
6950 View child = getChildAt(i);
6951 final Rect bounds = mRecyclerView.mTempRect;
6952 getDecoratedBoundsWithMargins(child, bounds);
6953 if (bounds.left < minX) {
6954 minX = bounds.left;
6955 }
6956 if (bounds.right > maxX) {
6957 maxX = bounds.right;
6958 }
6959 if (bounds.top < minY) {
6960 minY = bounds.top;
6961 }
6962 if (bounds.bottom > maxY) {
6963 maxY = bounds.bottom;
6964 }
6965 }
6966 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
6967 setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
6968 }
6969
6970 /**
6971 * Sets the measured dimensions from the given bounding box of the children and the
6972 * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is
6973 * called after the RecyclerView calls
6974 * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass.
6975 * <p>
6976 * This method should call {@link #setMeasuredDimension(int, int)}.
6977 * <p>
6978 * The default implementation adds the RecyclerView's padding to the given bounding box
6979 * then caps the value to be within the given measurement specs.
6980 * <p>
6981 * This method is only called if the LayoutManager opted into the auto measurement API.
6982 *
6983 * @param childrenBounds The bounding box of all children
6984 * @param wSpec The widthMeasureSpec that was passed into the RecyclerView.
6985 * @param hSpec The heightMeasureSpec that was passed into the RecyclerView.
6986 *
6987 * @see #setAutoMeasureEnabled(boolean)
6988 */
6989 public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
6990 int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
6991 int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
6992 int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
6993 int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
6994 setMeasuredDimension(width, height);
6995 }
6996
6997 /**
6998 * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
6999 */
7000 public void requestLayout() {
7001 if (mRecyclerView != null) {
7002 mRecyclerView.requestLayout();
7003 }
7004 }
7005
7006 /**
7007 * Checks if RecyclerView is in the middle of a layout or scroll and throws an
7008 * {@link IllegalStateException} if it <b>is not</b>.
7009 *
7010 * @param message The message for the exception. Can be null.
7011 * @see #assertNotInLayoutOrScroll(String)
7012 */
7013 public void assertInLayoutOrScroll(String message) {
7014 if (mRecyclerView != null) {
7015 mRecyclerView.assertInLayoutOrScroll(message);
7016 }
7017 }
7018
7019 /**
7020 * Chooses a size from the given specs and parameters that is closest to the desired size
7021 * and also complies with the spec.
7022 *
7023 * @param spec The measureSpec
7024 * @param desired The preferred measurement
7025 * @param min The minimum value
7026 *
7027 * @return A size that fits to the given specs
7028 */
7029 public static int chooseSize(int spec, int desired, int min) {
7030 final int mode = View.MeasureSpec.getMode(spec);
7031 final int size = View.MeasureSpec.getSize(spec);
7032 switch (mode) {
7033 case View.MeasureSpec.EXACTLY:
7034 return size;
7035 case View.MeasureSpec.AT_MOST:
7036 return Math.min(size, Math.max(desired, min));
7037 case View.MeasureSpec.UNSPECIFIED:
7038 default:
7039 return Math.max(desired, min);
7040 }
7041 }
7042
7043 /**
7044 * Checks if RecyclerView is in the middle of a layout or scroll and throws an
7045 * {@link IllegalStateException} if it <b>is</b>.
7046 *
7047 * @param message The message for the exception. Can be null.
7048 * @see #assertInLayoutOrScroll(String)
7049 */
7050 public void assertNotInLayoutOrScroll(String message) {
7051 if (mRecyclerView != null) {
7052 mRecyclerView.assertNotInLayoutOrScroll(message);
7053 }
7054 }
7055
7056 /**
7057 * Defines whether the layout should be measured by the RecyclerView or the LayoutManager
7058 * wants to handle the layout measurements itself.
7059 * <p>
7060 * This method is usually called by the LayoutManager with value {@code true} if it wants
7061 * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
7062 * the measurement logic, you can call this method with {@code false} and override
7063 * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
7064 * <p>
7065 * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
7066 * handle various specs provided by the RecyclerView's parent.
7067 * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an
7068 * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based
7069 * on children's positions. It does this while supporting all existing animation
7070 * capabilities of the RecyclerView.
7071 * <p>
7072 * AutoMeasure works as follows:
7073 * <ol>
7074 * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of
7075 * the framework LayoutManagers use {@code auto-measure}.</li>
7076 * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are
7077 * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without
7078 * doing any layout calculation.</li>
7079 * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the
7080 * layout process in {@code onMeasure} call. It will process all pending Adapter updates and
7081 * decide whether to run a predictive layout or not. If it decides to do so, it will first
7082 * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to
7083 * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still
7084 * return the width and height of the RecyclerView as of the last layout calculation.
7085 * <p>
7086 * After handling the predictive case, RecyclerView will call
7087 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
7088 * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can
7089 * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()},
7090 * {@link #getWidth()} and {@link #getWidthMode()}.</li>
7091 * <li>After the layout calculation, RecyclerView sets the measured width & height by
7092 * calculating the bounding box for the children (+ RecyclerView's padding). The
7093 * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose
7094 * different values. For instance, GridLayoutManager overrides this value to handle the case
7095 * where if it is vertical and has 3 columns but only 2 items, it should still measure its
7096 * width to fit 3 items, not 2.</li>
7097 * <li>Any following on measure call to the RecyclerView will run
7098 * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
7099 * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will
7100 * take care of which views are actually added / removed / moved / changed for animations so
7101 * that the LayoutManager should not worry about them and handle each
7102 * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
7103 * </li>
7104 * <li>When measure is complete and RecyclerView's
7105 * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks
7106 * whether it already did layout calculations during the measure pass and if so, it re-uses
7107 * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)}
7108 * if the last measure spec was different from the final dimensions or adapter contents
7109 * have changed between the measure call and the layout call.</li>
7110 * <li>Finally, animations are calculated and run as usual.</li>
7111 * </ol>
7112 *
7113 * @param enabled <code>True</code> if the Layout should be measured by the
7114 * RecyclerView, <code>false</code> if the LayoutManager wants
7115 * to measure itself.
7116 *
7117 * @see #setMeasuredDimension(Rect, int, int)
7118 * @see #isAutoMeasureEnabled()
7119 */
7120 public void setAutoMeasureEnabled(boolean enabled) {
7121 mAutoMeasure = enabled;
7122 }
7123
7124 /**
7125 * Returns whether the LayoutManager uses the automatic measurement API or not.
7126 *
7127 * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or
7128 * <code>false</code> if it measures itself.
7129 *
7130 * @see #setAutoMeasureEnabled(boolean)
7131 */
7132 public boolean isAutoMeasureEnabled() {
7133 return mAutoMeasure;
7134 }
7135
7136 /**
7137 * Returns whether this LayoutManager supports automatic item animations.
7138 * A LayoutManager wishing to support item animations should obey certain
7139 * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
7140 * The default return value is <code>false</code>, so subclasses of LayoutManager
7141 * will not get predictive item animations by default.
7142 *
7143 * <p>Whether item animations are enabled in a RecyclerView is determined both
7144 * by the return value from this method and the
7145 * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
7146 * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
7147 * method returns false, then simple item animations will be enabled, in which
7148 * views that are moving onto or off of the screen are simply faded in/out. If
7149 * the RecyclerView has a non-null ItemAnimator and this method returns true,
7150 * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
7151 * setup up the information needed to more intelligently predict where appearing
7152 * and disappearing views should be animated from/to.</p>
7153 *
7154 * @return true if predictive item animations should be enabled, false otherwise
7155 */
7156 public boolean supportsPredictiveItemAnimations() {
7157 return false;
7158 }
7159
7160 /**
7161 * Sets whether the LayoutManager should be queried for views outside of
7162 * its viewport while the UI thread is idle between frames.
7163 *
7164 * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between
7165 * view system traversals on devices running API 21 or greater. Default value is true.</p>
7166 *
7167 * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame
7168 * to RenderThread and the starting up its next frame at the next VSync pulse. By
7169 * prefetching out of window views in this time period, delays from inflation and view
7170 * binding are much less likely to cause jank and stuttering during scrolls and flings.</p>
7171 *
7172 * <p>While prefetch is enabled, it will have the side effect of expanding the effective
7173 * size of the View cache to hold prefetched views.</p>
7174 *
7175 * @param enabled <code>True</code> if items should be prefetched in between traversals.
7176 *
7177 * @see #isItemPrefetchEnabled()
7178 */
7179 public final void setItemPrefetchEnabled(boolean enabled) {
7180 if (enabled != mItemPrefetchEnabled) {
7181 mItemPrefetchEnabled = enabled;
7182 mPrefetchMaxCountObserved = 0;
7183 if (mRecyclerView != null) {
7184 mRecyclerView.mRecycler.updateViewCacheSize();
7185 }
7186 }
7187 }
7188
7189 /**
7190 * Sets whether the LayoutManager should be queried for views outside of
7191 * its viewport while the UI thread is idle between frames.
7192 *
7193 * @see #setItemPrefetchEnabled(boolean)
7194 *
7195 * @return true if item prefetch is enabled, false otherwise
7196 */
7197 public final boolean isItemPrefetchEnabled() {
7198 return mItemPrefetchEnabled;
7199 }
7200
7201 /**
7202 * Gather all positions from the LayoutManager to be prefetched, given specified momentum.
7203 *
7204 * <p>If item prefetch is enabled, this method is called in between traversals to gather
7205 * which positions the LayoutManager will soon need, given upcoming movement in subsequent
7206 * traversals.</p>
7207 *
7208 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
7209 * each item to be prepared, and these positions will have their ViewHolders created and
7210 * bound, if there is sufficient time available, in advance of being needed by a
7211 * scroll or layout.</p>
7212 *
7213 * @param dx X movement component.
7214 * @param dy Y movement component.
7215 * @param state State of RecyclerView
7216 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
7217 *
7218 * @see #isItemPrefetchEnabled()
7219 * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
7220 */
7221 public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
7222 LayoutPrefetchRegistry layoutPrefetchRegistry) {}
7223
7224 /**
7225 * Gather all positions from the LayoutManager to be prefetched in preperation for its
7226 * RecyclerView to come on screen, due to the movement of another, containing RecyclerView.
7227 *
7228 * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p>
7229 *
7230 * <p>If item prefetch is enabled for this LayoutManager, as well in another containing
7231 * LayoutManager, this method is called in between draw traversals to gather
7232 * which positions this LayoutManager will first need, once it appears on the screen.</p>
7233 *
7234 * <p>For example, if this LayoutManager represents a horizontally scrolling list within a
7235 * vertically scrolling LayoutManager, this method would be called when the horizontal list
7236 * is about to come onscreen.</p>
7237 *
7238 * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
7239 * each item to be prepared, and these positions will have their ViewHolders created and
7240 * bound, if there is sufficient time available, in advance of being needed by a
7241 * scroll or layout.</p>
7242 *
7243 * @param adapterItemCount number of items in the associated adapter.
7244 * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
7245 *
7246 * @see #isItemPrefetchEnabled()
7247 * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
7248 */
7249 public void collectInitialPrefetchPositions(int adapterItemCount,
7250 LayoutPrefetchRegistry layoutPrefetchRegistry) {}
7251
7252 void dispatchAttachedToWindow(RecyclerView view) {
7253 mIsAttachedToWindow = true;
7254 onAttachedToWindow(view);
7255 }
7256
7257 void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) {
7258 mIsAttachedToWindow = false;
7259 onDetachedFromWindow(view, recycler);
7260 }
7261
7262 /**
7263 * Returns whether LayoutManager is currently attached to a RecyclerView which is attached
7264 * to a window.
7265 *
7266 * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView
7267 * is attached to window.
7268 */
7269 public boolean isAttachedToWindow() {
7270 return mIsAttachedToWindow;
7271 }
7272
7273 /**
7274 * Causes the Runnable to execute on the next animation time step.
7275 * The runnable will be run on the user interface thread.
7276 * <p>
7277 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
7278 *
7279 * @param action The Runnable that will be executed.
7280 *
7281 * @see #removeCallbacks
7282 */
7283 public void postOnAnimation(Runnable action) {
7284 if (mRecyclerView != null) {
7285 mRecyclerView.postOnAnimation(action);
7286 }
7287 }
7288
7289 /**
7290 * Removes the specified Runnable from the message queue.
7291 * <p>
7292 * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
7293 *
7294 * @param action The Runnable to remove from the message handling queue
7295 *
7296 * @return true if RecyclerView could ask the Handler to remove the Runnable,
7297 * false otherwise. When the returned value is true, the Runnable
7298 * may or may not have been actually removed from the message queue
7299 * (for instance, if the Runnable was not in the queue already.)
7300 *
7301 * @see #postOnAnimation
7302 */
7303 public boolean removeCallbacks(Runnable action) {
7304 if (mRecyclerView != null) {
7305 return mRecyclerView.removeCallbacks(action);
7306 }
7307 return false;
7308 }
7309 /**
7310 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
7311 * is attached to a window.
7312 * <p>
7313 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
7314 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
7315 * not requested on the RecyclerView while it was detached.
7316 * <p>
7317 * Subclass implementations should always call through to the superclass implementation.
7318 *
7319 * @param view The RecyclerView this LayoutManager is bound to
7320 *
7321 * @see #onDetachedFromWindow(RecyclerView, Recycler)
7322 */
7323 @CallSuper
7324 public void onAttachedToWindow(RecyclerView view) {
7325 }
7326
7327 /**
7328 * @deprecated
7329 * override {@link #onDetachedFromWindow(RecyclerView, Recycler)}
7330 */
7331 @Deprecated
7332 public void onDetachedFromWindow(RecyclerView view) {
7333
7334 }
7335
7336 /**
7337 * Called when this LayoutManager is detached from its parent RecyclerView or when
7338 * its parent RecyclerView is detached from its window.
7339 * <p>
7340 * LayoutManager should clear all of its View references as another LayoutManager might be
7341 * assigned to the RecyclerView.
7342 * <p>
7343 * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
7344 * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
7345 * not requested on the RecyclerView while it was detached.
7346 * <p>
7347 * If your LayoutManager has View references that it cleans in on-detach, it should also
7348 * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when
7349 * RecyclerView is re-attached.
7350 * <p>
7351 * Subclass implementations should always call through to the superclass implementation.
7352 *
7353 * @param view The RecyclerView this LayoutManager is bound to
7354 * @param recycler The recycler to use if you prefer to recycle your children instead of
7355 * keeping them around.
7356 *
7357 * @see #onAttachedToWindow(RecyclerView)
7358 */
7359 @CallSuper
7360 public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
7361 onDetachedFromWindow(view);
7362 }
7363
7364 /**
7365 * Check if the RecyclerView is configured to clip child views to its padding.
7366 *
7367 * @return true if this RecyclerView clips children to its padding, false otherwise
7368 */
7369 public boolean getClipToPadding() {
7370 return mRecyclerView != null && mRecyclerView.mClipToPadding;
7371 }
7372
7373 /**
7374 * Lay out all relevant child views from the given adapter.
7375 *
7376 * The LayoutManager is in charge of the behavior of item animations. By default,
7377 * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple
7378 * item animations are enabled. This means that add/remove operations on the
7379 * adapter will result in animations to add new or appearing items, removed or
7380 * disappearing items, and moved items. If a LayoutManager returns false from
7381 * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a
7382 * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the
7383 * RecyclerView will have enough information to run those animations in a simple
7384 * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will
7385 * simply fade views in and out, whether they are actually added/removed or whether
7386 * they are moved on or off the screen due to other add/remove operations.
7387 *
7388 * <p>A LayoutManager wanting a better item animation experience, where items can be
7389 * animated onto and off of the screen according to where the items exist when they
7390 * are not on screen, then the LayoutManager should return true from
7391 * {@link #supportsPredictiveItemAnimations()} and add additional logic to
7392 * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations
7393 * means that {@link #onLayoutChildren(Recycler, State)} will be called twice;
7394 * once as a "pre" layout step to determine where items would have been prior to
7395 * a real layout, and again to do the "real" layout. In the pre-layout phase,
7396 * items will remember their pre-layout positions to allow them to be laid out
7397 * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will
7398 * be returned from the scrap to help determine correct placement of other items.
7399 * These removed items should not be added to the child list, but should be used
7400 * to help calculate correct positioning of other views, including views that
7401 * were not previously onscreen (referred to as APPEARING views), but whose
7402 * pre-layout offscreen position can be determined given the extra
7403 * information about the pre-layout removed views.</p>
7404 *
7405 * <p>The second layout pass is the real layout in which only non-removed views
7406 * will be used. The only additional requirement during this pass is, if
7407 * {@link #supportsPredictiveItemAnimations()} returns true, to note which
7408 * views exist in the child list prior to layout and which are not there after
7409 * layout (referred to as DISAPPEARING views), and to position/layout those views
7410 * appropriately, without regard to the actual bounds of the RecyclerView. This allows
7411 * the animation system to know the location to which to animate these disappearing
7412 * views.</p>
7413 *
7414 * <p>The default LayoutManager implementations for RecyclerView handle all of these
7415 * requirements for animations already. Clients of RecyclerView can either use one
7416 * of these layout managers directly or look at their implementations of
7417 * onLayoutChildren() to see how they account for the APPEARING and
7418 * DISAPPEARING views.</p>
7419 *
7420 * @param recycler Recycler to use for fetching potentially cached views for a
7421 * position
7422 * @param state Transient state of RecyclerView
7423 */
7424 public void onLayoutChildren(Recycler recycler, State state) {
7425 Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
7426 }
7427
7428 /**
7429 * Called after a full layout calculation is finished. The layout calculation may include
7430 * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or
7431 * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call.
7432 * This method will be called at the end of {@link View#layout(int, int, int, int)} call.
7433 * <p>
7434 * This is a good place for the LayoutManager to do some cleanup like pending scroll
7435 * position, saved state etc.
7436 *
7437 * @param state Transient state of RecyclerView
7438 */
7439 public void onLayoutCompleted(State state) {
7440 }
7441
7442 /**
7443 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
7444 *
7445 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
7446 * to store extra information specific to the layout. Client code should subclass
7447 * {@link RecyclerView.LayoutParams} for this purpose.</p>
7448 *
7449 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
7450 * you must also override
7451 * {@link #checkLayoutParams(LayoutParams)},
7452 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
7453 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
7454 *
7455 * @return A new LayoutParams for a child view
7456 */
7457 public abstract LayoutParams generateDefaultLayoutParams();
7458
7459 /**
7460 * Determines the validity of the supplied LayoutParams object.
7461 *
7462 * <p>This should check to make sure that the object is of the correct type
7463 * and all values are within acceptable ranges. The default implementation
7464 * returns <code>true</code> for non-null params.</p>
7465 *
7466 * @param lp LayoutParams object to check
7467 * @return true if this LayoutParams object is valid, false otherwise
7468 */
7469 public boolean checkLayoutParams(LayoutParams lp) {
7470 return lp != null;
7471 }
7472
7473 /**
7474 * Create a LayoutParams object suitable for this LayoutManager, copying relevant
7475 * values from the supplied LayoutParams object if possible.
7476 *
7477 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
7478 * you must also override
7479 * {@link #checkLayoutParams(LayoutParams)},
7480 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
7481 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
7482 *
7483 * @param lp Source LayoutParams object to copy values from
7484 * @return a new LayoutParams object
7485 */
7486 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
7487 if (lp instanceof LayoutParams) {
7488 return new LayoutParams((LayoutParams) lp);
7489 } else if (lp instanceof MarginLayoutParams) {
7490 return new LayoutParams((MarginLayoutParams) lp);
7491 } else {
7492 return new LayoutParams(lp);
7493 }
7494 }
7495
7496 /**
7497 * Create a LayoutParams object suitable for this LayoutManager from
7498 * an inflated layout resource.
7499 *
7500 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
7501 * you must also override
7502 * {@link #checkLayoutParams(LayoutParams)},
7503 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
7504 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
7505 *
7506 * @param c Context for obtaining styled attributes
7507 * @param attrs AttributeSet describing the supplied arguments
7508 * @return a new LayoutParams object
7509 */
7510 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
7511 return new LayoutParams(c, attrs);
7512 }
7513
7514 /**
7515 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
7516 * The default implementation does nothing and returns 0.
7517 *
7518 * @param dx distance to scroll by in pixels. X increases as scroll position
7519 * approaches the right.
7520 * @param recycler Recycler to use for fetching potentially cached views for a
7521 * position
7522 * @param state Transient state of RecyclerView
7523 * @return The actual distance scrolled. The return value will be negative if dx was
7524 * negative and scrolling proceeeded in that direction.
7525 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
7526 */
7527 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
7528 return 0;
7529 }
7530
7531 /**
7532 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
7533 * The default implementation does nothing and returns 0.
7534 *
7535 * @param dy distance to scroll in pixels. Y increases as scroll position
7536 * approaches the bottom.
7537 * @param recycler Recycler to use for fetching potentially cached views for a
7538 * position
7539 * @param state Transient state of RecyclerView
7540 * @return The actual distance scrolled. The return value will be negative if dy was
7541 * negative and scrolling proceeeded in that direction.
7542 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
7543 */
7544 public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
7545 return 0;
7546 }
7547
7548 /**
7549 * Query if horizontal scrolling is currently supported. The default implementation
7550 * returns false.
7551 *
7552 * @return True if this LayoutManager can scroll the current contents horizontally
7553 */
7554 public boolean canScrollHorizontally() {
7555 return false;
7556 }
7557
7558 /**
7559 * Query if vertical scrolling is currently supported. The default implementation
7560 * returns false.
7561 *
7562 * @return True if this LayoutManager can scroll the current contents vertically
7563 */
7564 public boolean canScrollVertically() {
7565 return false;
7566 }
7567
7568 /**
7569 * Scroll to the specified adapter position.
7570 *
7571 * Actual position of the item on the screen depends on the LayoutManager implementation.
7572 * @param position Scroll to this adapter position.
7573 */
7574 public void scrollToPosition(int position) {
7575 if (DEBUG) {
7576 Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
7577 }
7578 }
7579
7580 /**
7581 * <p>Smooth scroll to the specified adapter position.</p>
7582 * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
7583 * instance and call {@link #startSmoothScroll(SmoothScroller)}.
7584 * </p>
7585 * @param recyclerView The RecyclerView to which this layout manager is attached
7586 * @param state Current State of RecyclerView
7587 * @param position Scroll to this adapter position.
7588 */
7589 public void smoothScrollToPosition(RecyclerView recyclerView, State state,
7590 int position) {
7591 Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
7592 }
7593
7594 /**
7595 * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
7596 * <p>Calling this method will cancel any previous smooth scroll request.</p>
7597 * @param smoothScroller Instance which defines how smooth scroll should be animated
7598 */
7599 public void startSmoothScroll(SmoothScroller smoothScroller) {
7600 if (mSmoothScroller != null && smoothScroller != mSmoothScroller
7601 && mSmoothScroller.isRunning()) {
7602 mSmoothScroller.stop();
7603 }
7604 mSmoothScroller = smoothScroller;
7605 mSmoothScroller.start(mRecyclerView, this);
7606 }
7607
7608 /**
7609 * @return true if RecycylerView is currently in the state of smooth scrolling.
7610 */
7611 public boolean isSmoothScrolling() {
7612 return mSmoothScroller != null && mSmoothScroller.isRunning();
7613 }
7614
7615
7616 /**
7617 * Returns the resolved layout direction for this RecyclerView.
7618 *
7619 * @return {@link android.view.View#LAYOUT_DIRECTION_RTL} if the layout
7620 * direction is RTL or returns
7621 * {@link android.view.View#LAYOUT_DIRECTION_LTR} if the layout direction
7622 * is not RTL.
7623 */
7624 public int getLayoutDirection() {
7625 return mRecyclerView.getLayoutDirection();
7626 }
7627
7628 /**
7629 * Ends all animations on the view created by the {@link ItemAnimator}.
7630 *
7631 * @param view The View for which the animations should be ended.
7632 * @see RecyclerView.ItemAnimator#endAnimations()
7633 */
7634 public void endAnimation(View view) {
7635 if (mRecyclerView.mItemAnimator != null) {
7636 mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view));
7637 }
7638 }
7639
7640 /**
7641 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
7642 * to the layout that is known to be going away, either because it has been
7643 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
7644 * visible portion of the container but is being laid out in order to inform RecyclerView
7645 * in how to animate the item out of view.
7646 * <p>
7647 * Views added via this method are going to be invisible to LayoutManager after the
7648 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
7649 * or won't be included in {@link #getChildCount()} method.
7650 *
7651 * @param child View to add and then remove with animation.
7652 */
7653 public void addDisappearingView(View child) {
7654 addDisappearingView(child, -1);
7655 }
7656
7657 /**
7658 * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
7659 * to the layout that is known to be going away, either because it has been
7660 * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
7661 * visible portion of the container but is being laid out in order to inform RecyclerView
7662 * in how to animate the item out of view.
7663 * <p>
7664 * Views added via this method are going to be invisible to LayoutManager after the
7665 * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
7666 * or won't be included in {@link #getChildCount()} method.
7667 *
7668 * @param child View to add and then remove with animation.
7669 * @param index Index of the view.
7670 */
7671 public void addDisappearingView(View child, int index) {
7672 addViewInt(child, index, true);
7673 }
7674
7675 /**
7676 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
7677 * use this method to add views obtained from a {@link Recycler} using
7678 * {@link Recycler#getViewForPosition(int)}.
7679 *
7680 * @param child View to add
7681 */
7682 public void addView(View child) {
7683 addView(child, -1);
7684 }
7685
7686 /**
7687 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
7688 * use this method to add views obtained from a {@link Recycler} using
7689 * {@link Recycler#getViewForPosition(int)}.
7690 *
7691 * @param child View to add
7692 * @param index Index to add child at
7693 */
7694 public void addView(View child, int index) {
7695 addViewInt(child, index, false);
7696 }
7697
7698 private void addViewInt(View child, int index, boolean disappearing) {
7699 final ViewHolder holder = getChildViewHolderInt(child);
7700 if (disappearing || holder.isRemoved()) {
7701 // these views will be hidden at the end of the layout pass.
7702 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
7703 } else {
7704 // This may look like unnecessary but may happen if layout manager supports
7705 // predictive layouts and adapter removed then re-added the same item.
7706 // In this case, added version will be visible in the post layout (because add is
7707 // deferred) but RV will still bind it to the same View.
7708 // So if a View re-appears in post layout pass, remove it from disappearing list.
7709 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
7710 }
7711 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
7712 if (holder.wasReturnedFromScrap() || holder.isScrap()) {
7713 if (holder.isScrap()) {
7714 holder.unScrap();
7715 } else {
7716 holder.clearReturnedFromScrapFlag();
7717 }
7718 mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
7719 if (DISPATCH_TEMP_DETACH) {
7720 child.dispatchFinishTemporaryDetach();
7721 }
7722 } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
7723 // ensure in correct position
7724 int currentIndex = mChildHelper.indexOfChild(child);
7725 if (index == -1) {
7726 index = mChildHelper.getChildCount();
7727 }
7728 if (currentIndex == -1) {
7729 throw new IllegalStateException("Added View has RecyclerView as parent but"
7730 + " view is not a real child. Unfiltered index:"
7731 + mRecyclerView.indexOfChild(child));
7732 }
7733 if (currentIndex != index) {
7734 mRecyclerView.mLayout.moveView(currentIndex, index);
7735 }
7736 } else {
7737 mChildHelper.addView(child, index, false);
7738 lp.mInsetsDirty = true;
7739 if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
7740 mSmoothScroller.onChildAttachedToWindow(child);
7741 }
7742 }
7743 if (lp.mPendingInvalidate) {
7744 if (DEBUG) {
7745 Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
7746 }
7747 holder.itemView.invalidate();
7748 lp.mPendingInvalidate = false;
7749 }
7750 }
7751
7752 /**
7753 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
7754 * use this method to completely remove a child view that is no longer needed.
7755 * LayoutManagers should strongly consider recycling removed views using
7756 * {@link Recycler#recycleView(android.view.View)}.
7757 *
7758 * @param child View to remove
7759 */
7760 public void removeView(View child) {
7761 mChildHelper.removeView(child);
7762 }
7763
7764 /**
7765 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
7766 * use this method to completely remove a child view that is no longer needed.
7767 * LayoutManagers should strongly consider recycling removed views using
7768 * {@link Recycler#recycleView(android.view.View)}.
7769 *
7770 * @param index Index of the child view to remove
7771 */
7772 public void removeViewAt(int index) {
7773 final View child = getChildAt(index);
7774 if (child != null) {
7775 mChildHelper.removeViewAt(index);
7776 }
7777 }
7778
7779 /**
7780 * Remove all views from the currently attached RecyclerView. This will not recycle
7781 * any of the affected views; the LayoutManager is responsible for doing so if desired.
7782 */
7783 public void removeAllViews() {
7784 // Only remove non-animating views
7785 final int childCount = getChildCount();
7786 for (int i = childCount - 1; i >= 0; i--) {
7787 mChildHelper.removeViewAt(i);
7788 }
7789 }
7790
7791 /**
7792 * Returns offset of the RecyclerView's text baseline from the its top boundary.
7793 *
7794 * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if
7795 * there is no baseline.
7796 */
7797 public int getBaseline() {
7798 return -1;
7799 }
7800
7801 /**
7802 * Returns the adapter position of the item represented by the given View. This does not
7803 * contain any adapter changes that might have happened after the last layout.
7804 *
7805 * @param view The view to query
7806 * @return The adapter position of the item which is rendered by this View.
7807 */
7808 public int getPosition(View view) {
7809 return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
7810 }
7811
7812 /**
7813 * Returns the View type defined by the adapter.
7814 *
7815 * @param view The view to query
7816 * @return The type of the view assigned by the adapter.
7817 */
7818 public int getItemViewType(View view) {
7819 return getChildViewHolderInt(view).getItemViewType();
7820 }
7821
7822 /**
7823 * Traverses the ancestors of the given view and returns the item view that contains it
7824 * and also a direct child of the LayoutManager.
7825 * <p>
7826 * Note that this method may return null if the view is a child of the RecyclerView but
7827 * not a child of the LayoutManager (e.g. running a disappear animation).
7828 *
7829 * @param view The view that is a descendant of the LayoutManager.
7830 *
7831 * @return The direct child of the LayoutManager which contains the given view or null if
7832 * the provided view is not a descendant of this LayoutManager.
7833 *
7834 * @see RecyclerView#getChildViewHolder(View)
7835 * @see RecyclerView#findContainingViewHolder(View)
7836 */
7837 @Nullable
7838 public View findContainingItemView(View view) {
7839 if (mRecyclerView == null) {
7840 return null;
7841 }
7842 View found = mRecyclerView.findContainingItemView(view);
7843 if (found == null) {
7844 return null;
7845 }
7846 if (mChildHelper.isHidden(found)) {
7847 return null;
7848 }
7849 return found;
7850 }
7851
7852 /**
7853 * Finds the view which represents the given adapter position.
7854 * <p>
7855 * This method traverses each child since it has no information about child order.
7856 * Override this method to improve performance if your LayoutManager keeps data about
7857 * child views.
7858 * <p>
7859 * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method.
7860 *
7861 * @param position Position of the item in adapter
7862 * @return The child view that represents the given position or null if the position is not
7863 * laid out
7864 */
7865 public View findViewByPosition(int position) {
7866 final int childCount = getChildCount();
7867 for (int i = 0; i < childCount; i++) {
7868 View child = getChildAt(i);
7869 ViewHolder vh = getChildViewHolderInt(child);
7870 if (vh == null) {
7871 continue;
7872 }
7873 if (vh.getLayoutPosition() == position && !vh.shouldIgnore()
7874 && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
7875 return child;
7876 }
7877 }
7878 return null;
7879 }
7880
7881 /**
7882 * Temporarily detach a child view.
7883 *
7884 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
7885 * views currently attached to the RecyclerView. Generally LayoutManager implementations
7886 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
7887 * so that the detached view may be rebound and reused.</p>
7888 *
7889 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
7890 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
7891 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
7892 * before the LayoutManager entry point method called by RecyclerView returns.</p>
7893 *
7894 * @param child Child to detach
7895 */
7896 public void detachView(View child) {
7897 final int ind = mChildHelper.indexOfChild(child);
7898 if (ind >= 0) {
7899 detachViewInternal(ind, child);
7900 }
7901 }
7902
7903 /**
7904 * Temporarily detach a child view.
7905 *
7906 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
7907 * views currently attached to the RecyclerView. Generally LayoutManager implementations
7908 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
7909 * so that the detached view may be rebound and reused.</p>
7910 *
7911 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
7912 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
7913 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
7914 * before the LayoutManager entry point method called by RecyclerView returns.</p>
7915 *
7916 * @param index Index of the child to detach
7917 */
7918 public void detachViewAt(int index) {
7919 detachViewInternal(index, getChildAt(index));
7920 }
7921
7922 private void detachViewInternal(int index, View view) {
7923 if (DISPATCH_TEMP_DETACH) {
7924 view.dispatchStartTemporaryDetach();
7925 }
7926 mChildHelper.detachViewFromParent(index);
7927 }
7928
7929 /**
7930 * Reattach a previously {@link #detachView(android.view.View) detached} view.
7931 * This method should not be used to reattach views that were previously
7932 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
7933 *
7934 * @param child Child to reattach
7935 * @param index Intended child index for child
7936 * @param lp LayoutParams for child
7937 */
7938 public void attachView(View child, int index, LayoutParams lp) {
7939 ViewHolder vh = getChildViewHolderInt(child);
7940 if (vh.isRemoved()) {
7941 mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh);
7942 } else {
7943 mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh);
7944 }
7945 mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
7946 if (DISPATCH_TEMP_DETACH) {
7947 child.dispatchFinishTemporaryDetach();
7948 }
7949 }
7950
7951 /**
7952 * Reattach a previously {@link #detachView(android.view.View) detached} view.
7953 * This method should not be used to reattach views that were previously
7954 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
7955 *
7956 * @param child Child to reattach
7957 * @param index Intended child index for child
7958 */
7959 public void attachView(View child, int index) {
7960 attachView(child, index, (LayoutParams) child.getLayoutParams());
7961 }
7962
7963 /**
7964 * Reattach a previously {@link #detachView(android.view.View) detached} view.
7965 * This method should not be used to reattach views that were previously
7966 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
7967 *
7968 * @param child Child to reattach
7969 */
7970 public void attachView(View child) {
7971 attachView(child, -1);
7972 }
7973
7974 /**
7975 * Finish removing a view that was previously temporarily
7976 * {@link #detachView(android.view.View) detached}.
7977 *
7978 * @param child Detached child to remove
7979 */
7980 public void removeDetachedView(View child) {
7981 mRecyclerView.removeDetachedView(child, false);
7982 }
7983
7984 /**
7985 * Moves a View from one position to another.
7986 *
7987 * @param fromIndex The View's initial index
7988 * @param toIndex The View's target index
7989 */
7990 public void moveView(int fromIndex, int toIndex) {
7991 View view = getChildAt(fromIndex);
7992 if (view == null) {
7993 throw new IllegalArgumentException("Cannot move a child from non-existing index:"
7994 + fromIndex);
7995 }
7996 detachViewAt(fromIndex);
7997 attachView(view, toIndex);
7998 }
7999
8000 /**
8001 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
8002 *
8003 * <p>Scrapping a view allows it to be rebound and reused to show updated or
8004 * different data.</p>
8005 *
8006 * @param child Child to detach and scrap
8007 * @param recycler Recycler to deposit the new scrap view into
8008 */
8009 public void detachAndScrapView(View child, Recycler recycler) {
8010 int index = mChildHelper.indexOfChild(child);
8011 scrapOrRecycleView(recycler, index, child);
8012 }
8013
8014 /**
8015 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
8016 *
8017 * <p>Scrapping a view allows it to be rebound and reused to show updated or
8018 * different data.</p>
8019 *
8020 * @param index Index of child to detach and scrap
8021 * @param recycler Recycler to deposit the new scrap view into
8022 */
8023 public void detachAndScrapViewAt(int index, Recycler recycler) {
8024 final View child = getChildAt(index);
8025 scrapOrRecycleView(recycler, index, child);
8026 }
8027
8028 /**
8029 * Remove a child view and recycle it using the given Recycler.
8030 *
8031 * @param child Child to remove and recycle
8032 * @param recycler Recycler to use to recycle child
8033 */
8034 public void removeAndRecycleView(View child, Recycler recycler) {
8035 removeView(child);
8036 recycler.recycleView(child);
8037 }
8038
8039 /**
8040 * Remove a child view and recycle it using the given Recycler.
8041 *
8042 * @param index Index of child to remove and recycle
8043 * @param recycler Recycler to use to recycle child
8044 */
8045 public void removeAndRecycleViewAt(int index, Recycler recycler) {
8046 final View view = getChildAt(index);
8047 removeViewAt(index);
8048 recycler.recycleView(view);
8049 }
8050
8051 /**
8052 * Return the current number of child views attached to the parent RecyclerView.
8053 * This does not include child views that were temporarily detached and/or scrapped.
8054 *
8055 * @return Number of attached children
8056 */
8057 public int getChildCount() {
8058 return mChildHelper != null ? mChildHelper.getChildCount() : 0;
8059 }
8060
8061 /**
8062 * Return the child view at the given index
8063 * @param index Index of child to return
8064 * @return Child view at index
8065 */
8066 public View getChildAt(int index) {
8067 return mChildHelper != null ? mChildHelper.getChildAt(index) : null;
8068 }
8069
8070 /**
8071 * Return the width measurement spec mode of the RecyclerView.
8072 * <p>
8073 * This value is set only if the LayoutManager opts into the auto measure api via
8074 * {@link #setAutoMeasureEnabled(boolean)}.
8075 * <p>
8076 * When RecyclerView is running a layout, this value is always set to
8077 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
8078 *
8079 * @return Width measure spec mode.
8080 *
8081 * @see View.MeasureSpec#getMode(int)
8082 * @see View#onMeasure(int, int)
8083 */
8084 public int getWidthMode() {
8085 return mWidthMode;
8086 }
8087
8088 /**
8089 * Return the height measurement spec mode of the RecyclerView.
8090 * <p>
8091 * This value is set only if the LayoutManager opts into the auto measure api via
8092 * {@link #setAutoMeasureEnabled(boolean)}.
8093 * <p>
8094 * When RecyclerView is running a layout, this value is always set to
8095 * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
8096 *
8097 * @return Height measure spec mode.
8098 *
8099 * @see View.MeasureSpec#getMode(int)
8100 * @see View#onMeasure(int, int)
8101 */
8102 public int getHeightMode() {
8103 return mHeightMode;
8104 }
8105
8106 /**
8107 * Return the width of the parent RecyclerView
8108 *
8109 * @return Width in pixels
8110 */
8111 public int getWidth() {
8112 return mWidth;
8113 }
8114
8115 /**
8116 * Return the height of the parent RecyclerView
8117 *
8118 * @return Height in pixels
8119 */
8120 public int getHeight() {
8121 return mHeight;
8122 }
8123
8124 /**
8125 * Return the left padding of the parent RecyclerView
8126 *
8127 * @return Padding in pixels
8128 */
8129 public int getPaddingLeft() {
8130 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
8131 }
8132
8133 /**
8134 * Return the top padding of the parent RecyclerView
8135 *
8136 * @return Padding in pixels
8137 */
8138 public int getPaddingTop() {
8139 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
8140 }
8141
8142 /**
8143 * Return the right padding of the parent RecyclerView
8144 *
8145 * @return Padding in pixels
8146 */
8147 public int getPaddingRight() {
8148 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
8149 }
8150
8151 /**
8152 * Return the bottom padding of the parent RecyclerView
8153 *
8154 * @return Padding in pixels
8155 */
8156 public int getPaddingBottom() {
8157 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
8158 }
8159
8160 /**
8161 * Return the start padding of the parent RecyclerView
8162 *
8163 * @return Padding in pixels
8164 */
8165 public int getPaddingStart() {
8166 return mRecyclerView != null ? mRecyclerView.getPaddingStart() : 0;
8167 }
8168
8169 /**
8170 * Return the end padding of the parent RecyclerView
8171 *
8172 * @return Padding in pixels
8173 */
8174 public int getPaddingEnd() {
8175 return mRecyclerView != null ? mRecyclerView.getPaddingEnd() : 0;
8176 }
8177
8178 /**
8179 * Returns true if the RecyclerView this LayoutManager is bound to has focus.
8180 *
8181 * @return True if the RecyclerView has focus, false otherwise.
8182 * @see View#isFocused()
8183 */
8184 public boolean isFocused() {
8185 return mRecyclerView != null && mRecyclerView.isFocused();
8186 }
8187
8188 /**
8189 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
8190 *
8191 * @return true if the RecyclerView has or contains focus
8192 * @see View#hasFocus()
8193 */
8194 public boolean hasFocus() {
8195 return mRecyclerView != null && mRecyclerView.hasFocus();
8196 }
8197
8198 /**
8199 * Returns the item View which has or contains focus.
8200 *
8201 * @return A direct child of RecyclerView which has focus or contains the focused child.
8202 */
8203 public View getFocusedChild() {
8204 if (mRecyclerView == null) {
8205 return null;
8206 }
8207 final View focused = mRecyclerView.getFocusedChild();
8208 if (focused == null || mChildHelper.isHidden(focused)) {
8209 return null;
8210 }
8211 return focused;
8212 }
8213
8214 /**
8215 * Returns the number of items in the adapter bound to the parent RecyclerView.
8216 * <p>
8217 * Note that this number is not necessarily equal to
8218 * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is
8219 * available, you should use {@link State#getItemCount() State#getItemCount()} instead.
8220 * For more details, check the documentation for
8221 * {@link State#getItemCount() State#getItemCount()}.
8222 *
8223 * @return The number of items in the bound adapter
8224 * @see State#getItemCount()
8225 */
8226 public int getItemCount() {
8227 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
8228 return a != null ? a.getItemCount() : 0;
8229 }
8230
8231 /**
8232 * Offset all child views attached to the parent RecyclerView by dx pixels along
8233 * the horizontal axis.
8234 *
8235 * @param dx Pixels to offset by
8236 */
8237 public void offsetChildrenHorizontal(int dx) {
8238 if (mRecyclerView != null) {
8239 mRecyclerView.offsetChildrenHorizontal(dx);
8240 }
8241 }
8242
8243 /**
8244 * Offset all child views attached to the parent RecyclerView by dy pixels along
8245 * the vertical axis.
8246 *
8247 * @param dy Pixels to offset by
8248 */
8249 public void offsetChildrenVertical(int dy) {
8250 if (mRecyclerView != null) {
8251 mRecyclerView.offsetChildrenVertical(dy);
8252 }
8253 }
8254
8255 /**
8256 * Flags a view so that it will not be scrapped or recycled.
8257 * <p>
8258 * Scope of ignoring a child is strictly restricted to position tracking, scrapping and
8259 * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child
8260 * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not
8261 * ignore the child.
8262 * <p>
8263 * Before this child can be recycled again, you have to call
8264 * {@link #stopIgnoringView(View)}.
8265 * <p>
8266 * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
8267 *
8268 * @param view View to ignore.
8269 * @see #stopIgnoringView(View)
8270 */
8271 public void ignoreView(View view) {
8272 if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) {
8273 // checking this because calling this method on a recycled or detached view may
8274 // cause loss of state.
8275 throw new IllegalArgumentException("View should be fully attached to be ignored");
8276 }
8277 final ViewHolder vh = getChildViewHolderInt(view);
8278 vh.addFlags(ViewHolder.FLAG_IGNORE);
8279 mRecyclerView.mViewInfoStore.removeViewHolder(vh);
8280 }
8281
8282 /**
8283 * View can be scrapped and recycled again.
8284 * <p>
8285 * Note that calling this method removes all information in the view holder.
8286 * <p>
8287 * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
8288 *
8289 * @param view View to ignore.
8290 */
8291 public void stopIgnoringView(View view) {
8292 final ViewHolder vh = getChildViewHolderInt(view);
8293 vh.stopIgnoring();
8294 vh.resetInternal();
8295 vh.addFlags(ViewHolder.FLAG_INVALID);
8296 }
8297
8298 /**
8299 * Temporarily detach and scrap all currently attached child views. Views will be scrapped
8300 * into the given Recycler. The Recycler may prefer to reuse scrap views before
8301 * other views that were previously recycled.
8302 *
8303 * @param recycler Recycler to scrap views into
8304 */
8305 public void detachAndScrapAttachedViews(Recycler recycler) {
8306 final int childCount = getChildCount();
8307 for (int i = childCount - 1; i >= 0; i--) {
8308 final View v = getChildAt(i);
8309 scrapOrRecycleView(recycler, i, v);
8310 }
8311 }
8312
8313 private void scrapOrRecycleView(Recycler recycler, int index, View view) {
8314 final ViewHolder viewHolder = getChildViewHolderInt(view);
8315 if (viewHolder.shouldIgnore()) {
8316 if (DEBUG) {
8317 Log.d(TAG, "ignoring view " + viewHolder);
8318 }
8319 return;
8320 }
8321 if (viewHolder.isInvalid() && !viewHolder.isRemoved()
8322 && !mRecyclerView.mAdapter.hasStableIds()) {
8323 removeViewAt(index);
8324 recycler.recycleViewHolderInternal(viewHolder);
8325 } else {
8326 detachViewAt(index);
8327 recycler.scrapView(view);
8328 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
8329 }
8330 }
8331
8332 /**
8333 * Recycles the scrapped views.
8334 * <p>
8335 * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is
8336 * the expected behavior if scrapped views are used for animations. Otherwise, we need to
8337 * call remove and invalidate RecyclerView to ensure UI update.
8338 *
8339 * @param recycler Recycler
8340 */
8341 void removeAndRecycleScrapInt(Recycler recycler) {
8342 final int scrapCount = recycler.getScrapCount();
8343 // Loop backward, recycler might be changed by removeDetachedView()
8344 for (int i = scrapCount - 1; i >= 0; i--) {
8345 final View scrap = recycler.getScrapViewAt(i);
8346 final ViewHolder vh = getChildViewHolderInt(scrap);
8347 if (vh.shouldIgnore()) {
8348 continue;
8349 }
8350 // If the scrap view is animating, we need to cancel them first. If we cancel it
8351 // here, ItemAnimator callback may recycle it which will cause double recycling.
8352 // To avoid this, we mark it as not recycleable before calling the item animator.
8353 // Since removeDetachedView calls a user API, a common mistake (ending animations on
8354 // the view) may recycle it too, so we guard it before we call user APIs.
8355 vh.setIsRecyclable(false);
8356 if (vh.isTmpDetached()) {
8357 mRecyclerView.removeDetachedView(scrap, false);
8358 }
8359 if (mRecyclerView.mItemAnimator != null) {
8360 mRecyclerView.mItemAnimator.endAnimation(vh);
8361 }
8362 vh.setIsRecyclable(true);
8363 recycler.quickRecycleScrapView(scrap);
8364 }
8365 recycler.clearScrap();
8366 if (scrapCount > 0) {
8367 mRecyclerView.invalidate();
8368 }
8369 }
8370
8371
8372 /**
8373 * Measure a child view using standard measurement policy, taking the padding
8374 * of the parent RecyclerView and any added item decorations into account.
8375 *
8376 * <p>If the RecyclerView can be scrolled in either dimension the caller may
8377 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
8378 *
8379 * @param child Child view to measure
8380 * @param widthUsed Width in pixels currently consumed by other views, if relevant
8381 * @param heightUsed Height in pixels currently consumed by other views, if relevant
8382 */
8383 public void measureChild(View child, int widthUsed, int heightUsed) {
8384 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
8385
8386 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
8387 widthUsed += insets.left + insets.right;
8388 heightUsed += insets.top + insets.bottom;
8389 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
8390 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
8391 canScrollHorizontally());
8392 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
8393 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
8394 canScrollVertically());
8395 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
8396 child.measure(widthSpec, heightSpec);
8397 }
8398 }
8399
8400 /**
8401 * RecyclerView internally does its own View measurement caching which should help with
8402 * WRAP_CONTENT.
8403 * <p>
8404 * Use this method if the View is already measured once in this layout pass.
8405 */
8406 boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
8407 return !mMeasurementCacheEnabled
8408 || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)
8409 || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);
8410 }
8411
8412 // we may consider making this public
8413 /**
8414 * RecyclerView internally does its own View measurement caching which should help with
8415 * WRAP_CONTENT.
8416 * <p>
8417 * Use this method if the View is not yet measured and you need to decide whether to
8418 * measure this View or not.
8419 */
8420 boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
8421 return child.isLayoutRequested()
8422 || !mMeasurementCacheEnabled
8423 || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
8424 || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
8425 }
8426
8427 /**
8428 * In addition to the View Framework's measurement cache, RecyclerView uses its own
8429 * additional measurement cache for its children to avoid re-measuring them when not
8430 * necessary. It is on by default but it can be turned off via
8431 * {@link #setMeasurementCacheEnabled(boolean)}.
8432 *
8433 * @return True if measurement cache is enabled, false otherwise.
8434 *
8435 * @see #setMeasurementCacheEnabled(boolean)
8436 */
8437 public boolean isMeasurementCacheEnabled() {
8438 return mMeasurementCacheEnabled;
8439 }
8440
8441 /**
8442 * Sets whether RecyclerView should use its own measurement cache for the children. This is
8443 * a more aggressive cache than the framework uses.
8444 *
8445 * @param measurementCacheEnabled True to enable the measurement cache, false otherwise.
8446 *
8447 * @see #isMeasurementCacheEnabled()
8448 */
8449 public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) {
8450 mMeasurementCacheEnabled = measurementCacheEnabled;
8451 }
8452
8453 private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
8454 final int specMode = MeasureSpec.getMode(spec);
8455 final int specSize = MeasureSpec.getSize(spec);
8456 if (dimension > 0 && childSize != dimension) {
8457 return false;
8458 }
8459 switch (specMode) {
8460 case MeasureSpec.UNSPECIFIED:
8461 return true;
8462 case MeasureSpec.AT_MOST:
8463 return specSize >= childSize;
8464 case MeasureSpec.EXACTLY:
8465 return specSize == childSize;
8466 }
8467 return false;
8468 }
8469
8470 /**
8471 * Measure a child view using standard measurement policy, taking the padding
8472 * of the parent RecyclerView, any added item decorations and the child margins
8473 * into account.
8474 *
8475 * <p>If the RecyclerView can be scrolled in either dimension the caller may
8476 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
8477 *
8478 * @param child Child view to measure
8479 * @param widthUsed Width in pixels currently consumed by other views, if relevant
8480 * @param heightUsed Height in pixels currently consumed by other views, if relevant
8481 */
8482 public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
8483 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
8484
8485 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
8486 widthUsed += insets.left + insets.right;
8487 heightUsed += insets.top + insets.bottom;
8488
8489 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
8490 getPaddingLeft() + getPaddingRight()
8491 + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
8492 canScrollHorizontally());
8493 final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
8494 getPaddingTop() + getPaddingBottom()
8495 + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
8496 canScrollVertically());
8497 if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
8498 child.measure(widthSpec, heightSpec);
8499 }
8500 }
8501
8502 /**
8503 * Calculate a MeasureSpec value for measuring a child view in one dimension.
8504 *
8505 * @param parentSize Size of the parent view where the child will be placed
8506 * @param padding Total space currently consumed by other elements of the parent
8507 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
8508 * Generally obtained from the child view's LayoutParams
8509 * @param canScroll true if the parent RecyclerView can scroll in this dimension
8510 *
8511 * @return a MeasureSpec value for the child view
8512 * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)}
8513 */
8514 @Deprecated
8515 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
8516 boolean canScroll) {
8517 int size = Math.max(0, parentSize - padding);
8518 int resultSize = 0;
8519 int resultMode = 0;
8520 if (canScroll) {
8521 if (childDimension >= 0) {
8522 resultSize = childDimension;
8523 resultMode = MeasureSpec.EXACTLY;
8524 } else {
8525 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
8526 // instead using UNSPECIFIED.
8527 resultSize = 0;
8528 resultMode = MeasureSpec.UNSPECIFIED;
8529 }
8530 } else {
8531 if (childDimension >= 0) {
8532 resultSize = childDimension;
8533 resultMode = MeasureSpec.EXACTLY;
8534 } else if (childDimension == LayoutParams.MATCH_PARENT) {
8535 resultSize = size;
8536 // TODO this should be my spec.
8537 resultMode = MeasureSpec.EXACTLY;
8538 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
8539 resultSize = size;
8540 resultMode = MeasureSpec.AT_MOST;
8541 }
8542 }
8543 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
8544 }
8545
8546 /**
8547 * Calculate a MeasureSpec value for measuring a child view in one dimension.
8548 *
8549 * @param parentSize Size of the parent view where the child will be placed
8550 * @param parentMode The measurement spec mode of the parent
8551 * @param padding Total space currently consumed by other elements of parent
8552 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
8553 * Generally obtained from the child view's LayoutParams
8554 * @param canScroll true if the parent RecyclerView can scroll in this dimension
8555 *
8556 * @return a MeasureSpec value for the child view
8557 */
8558 public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
8559 int childDimension, boolean canScroll) {
8560 int size = Math.max(0, parentSize - padding);
8561 int resultSize = 0;
8562 int resultMode = 0;
8563 if (canScroll) {
8564 if (childDimension >= 0) {
8565 resultSize = childDimension;
8566 resultMode = MeasureSpec.EXACTLY;
8567 } else if (childDimension == LayoutParams.MATCH_PARENT) {
8568 switch (parentMode) {
8569 case MeasureSpec.AT_MOST:
8570 case MeasureSpec.EXACTLY:
8571 resultSize = size;
8572 resultMode = parentMode;
8573 break;
8574 case MeasureSpec.UNSPECIFIED:
8575 resultSize = 0;
8576 resultMode = MeasureSpec.UNSPECIFIED;
8577 break;
8578 }
8579 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
8580 resultSize = 0;
8581 resultMode = MeasureSpec.UNSPECIFIED;
8582 }
8583 } else {
8584 if (childDimension >= 0) {
8585 resultSize = childDimension;
8586 resultMode = MeasureSpec.EXACTLY;
8587 } else if (childDimension == LayoutParams.MATCH_PARENT) {
8588 resultSize = size;
8589 resultMode = parentMode;
8590 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
8591 resultSize = size;
8592 if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
8593 resultMode = MeasureSpec.AT_MOST;
8594 } else {
8595 resultMode = MeasureSpec.UNSPECIFIED;
8596 }
8597
8598 }
8599 }
8600 //noinspection WrongConstant
8601 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
8602 }
8603
8604 /**
8605 * Returns the measured width of the given child, plus the additional size of
8606 * any insets applied by {@link ItemDecoration ItemDecorations}.
8607 *
8608 * @param child Child view to query
8609 * @return child's measured width plus <code>ItemDecoration</code> insets
8610 *
8611 * @see View#getMeasuredWidth()
8612 */
8613 public int getDecoratedMeasuredWidth(View child) {
8614 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
8615 return child.getMeasuredWidth() + insets.left + insets.right;
8616 }
8617
8618 /**
8619 * Returns the measured height of the given child, plus the additional size of
8620 * any insets applied by {@link ItemDecoration ItemDecorations}.
8621 *
8622 * @param child Child view to query
8623 * @return child's measured height plus <code>ItemDecoration</code> insets
8624 *
8625 * @see View#getMeasuredHeight()
8626 */
8627 public int getDecoratedMeasuredHeight(View child) {
8628 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
8629 return child.getMeasuredHeight() + insets.top + insets.bottom;
8630 }
8631
8632 /**
8633 * Lay out the given child view within the RecyclerView using coordinates that
8634 * include any current {@link ItemDecoration ItemDecorations}.
8635 *
8636 * <p>LayoutManagers should prefer working in sizes and coordinates that include
8637 * item decoration insets whenever possible. This allows the LayoutManager to effectively
8638 * ignore decoration insets within measurement and layout code. See the following
8639 * methods:</p>
8640 * <ul>
8641 * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li>
8642 * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li>
8643 * <li>{@link #measureChild(View, int, int)}</li>
8644 * <li>{@link #measureChildWithMargins(View, int, int)}</li>
8645 * <li>{@link #getDecoratedLeft(View)}</li>
8646 * <li>{@link #getDecoratedTop(View)}</li>
8647 * <li>{@link #getDecoratedRight(View)}</li>
8648 * <li>{@link #getDecoratedBottom(View)}</li>
8649 * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
8650 * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
8651 * </ul>
8652 *
8653 * @param child Child to lay out
8654 * @param left Left edge, with item decoration insets included
8655 * @param top Top edge, with item decoration insets included
8656 * @param right Right edge, with item decoration insets included
8657 * @param bottom Bottom edge, with item decoration insets included
8658 *
8659 * @see View#layout(int, int, int, int)
8660 * @see #layoutDecoratedWithMargins(View, int, int, int, int)
8661 */
8662 public void layoutDecorated(View child, int left, int top, int right, int bottom) {
8663 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
8664 child.layout(left + insets.left, top + insets.top, right - insets.right,
8665 bottom - insets.bottom);
8666 }
8667
8668 /**
8669 * Lay out the given child view within the RecyclerView using coordinates that
8670 * include any current {@link ItemDecoration ItemDecorations} and margins.
8671 *
8672 * <p>LayoutManagers should prefer working in sizes and coordinates that include
8673 * item decoration insets whenever possible. This allows the LayoutManager to effectively
8674 * ignore decoration insets within measurement and layout code. See the following
8675 * methods:</p>
8676 * <ul>
8677 * <li>{@link #layoutDecorated(View, int, int, int, int)}</li>
8678 * <li>{@link #measureChild(View, int, int)}</li>
8679 * <li>{@link #measureChildWithMargins(View, int, int)}</li>
8680 * <li>{@link #getDecoratedLeft(View)}</li>
8681 * <li>{@link #getDecoratedTop(View)}</li>
8682 * <li>{@link #getDecoratedRight(View)}</li>
8683 * <li>{@link #getDecoratedBottom(View)}</li>
8684 * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
8685 * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
8686 * </ul>
8687 *
8688 * @param child Child to lay out
8689 * @param left Left edge, with item decoration insets and left margin included
8690 * @param top Top edge, with item decoration insets and top margin included
8691 * @param right Right edge, with item decoration insets and right margin included
8692 * @param bottom Bottom edge, with item decoration insets and bottom margin included
8693 *
8694 * @see View#layout(int, int, int, int)
8695 * @see #layoutDecorated(View, int, int, int, int)
8696 */
8697 public void layoutDecoratedWithMargins(View child, int left, int top, int right,
8698 int bottom) {
8699 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
8700 final Rect insets = lp.mDecorInsets;
8701 child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
8702 right - insets.right - lp.rightMargin,
8703 bottom - insets.bottom - lp.bottomMargin);
8704 }
8705
8706 /**
8707 * Calculates the bounding box of the View while taking into account its matrix changes
8708 * (translation, scale etc) with respect to the RecyclerView.
8709 * <p>
8710 * If {@code includeDecorInsets} is {@code true}, they are applied first before applying
8711 * the View's matrix so that the decor offsets also go through the same transformation.
8712 *
8713 * @param child The ItemView whose bounding box should be calculated.
8714 * @param includeDecorInsets True if the decor insets should be included in the bounding box
8715 * @param out The rectangle into which the output will be written.
8716 */
8717 public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) {
8718 if (includeDecorInsets) {
8719 Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
8720 out.set(-insets.left, -insets.top,
8721 child.getWidth() + insets.right, child.getHeight() + insets.bottom);
8722 } else {
8723 out.set(0, 0, child.getWidth(), child.getHeight());
8724 }
8725
8726 if (mRecyclerView != null) {
8727 final Matrix childMatrix = child.getMatrix();
8728 if (childMatrix != null && !childMatrix.isIdentity()) {
8729 final RectF tempRectF = mRecyclerView.mTempRectF;
8730 tempRectF.set(out);
8731 childMatrix.mapRect(tempRectF);
8732 out.set(
8733 (int) Math.floor(tempRectF.left),
8734 (int) Math.floor(tempRectF.top),
8735 (int) Math.ceil(tempRectF.right),
8736 (int) Math.ceil(tempRectF.bottom)
8737 );
8738 }
8739 }
8740 out.offset(child.getLeft(), child.getTop());
8741 }
8742
8743 /**
8744 * Returns the bounds of the view including its decoration and margins.
8745 *
8746 * @param view The view element to check
8747 * @param outBounds A rect that will receive the bounds of the element including its
8748 * decoration and margins.
8749 */
8750 public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
8751 RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds);
8752 }
8753
8754 /**
8755 * Returns the left edge of the given child view within its parent, offset by any applied
8756 * {@link ItemDecoration ItemDecorations}.
8757 *
8758 * @param child Child to query
8759 * @return Child left edge with offsets applied
8760 * @see #getLeftDecorationWidth(View)
8761 */
8762 public int getDecoratedLeft(View child) {
8763 return child.getLeft() - getLeftDecorationWidth(child);
8764 }
8765
8766 /**
8767 * Returns the top edge of the given child view within its parent, offset by any applied
8768 * {@link ItemDecoration ItemDecorations}.
8769 *
8770 * @param child Child to query
8771 * @return Child top edge with offsets applied
8772 * @see #getTopDecorationHeight(View)
8773 */
8774 public int getDecoratedTop(View child) {
8775 return child.getTop() - getTopDecorationHeight(child);
8776 }
8777
8778 /**
8779 * Returns the right edge of the given child view within its parent, offset by any applied
8780 * {@link ItemDecoration ItemDecorations}.
8781 *
8782 * @param child Child to query
8783 * @return Child right edge with offsets applied
8784 * @see #getRightDecorationWidth(View)
8785 */
8786 public int getDecoratedRight(View child) {
8787 return child.getRight() + getRightDecorationWidth(child);
8788 }
8789
8790 /**
8791 * Returns the bottom edge of the given child view within its parent, offset by any applied
8792 * {@link ItemDecoration ItemDecorations}.
8793 *
8794 * @param child Child to query
8795 * @return Child bottom edge with offsets applied
8796 * @see #getBottomDecorationHeight(View)
8797 */
8798 public int getDecoratedBottom(View child) {
8799 return child.getBottom() + getBottomDecorationHeight(child);
8800 }
8801
8802 /**
8803 * Calculates the item decor insets applied to the given child and updates the provided
8804 * Rect instance with the inset values.
8805 * <ul>
8806 * <li>The Rect's left is set to the total width of left decorations.</li>
8807 * <li>The Rect's top is set to the total height of top decorations.</li>
8808 * <li>The Rect's right is set to the total width of right decorations.</li>
8809 * <li>The Rect's bottom is set to total height of bottom decorations.</li>
8810 * </ul>
8811 * <p>
8812 * Note that item decorations are automatically calculated when one of the LayoutManager's
8813 * measure child methods is called. If you need to measure the child with custom specs via
8814 * {@link View#measure(int, int)}, you can use this method to get decorations.
8815 *
8816 * @param child The child view whose decorations should be calculated
8817 * @param outRect The Rect to hold result values
8818 */
8819 public void calculateItemDecorationsForChild(View child, Rect outRect) {
8820 if (mRecyclerView == null) {
8821 outRect.set(0, 0, 0, 0);
8822 return;
8823 }
8824 Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
8825 outRect.set(insets);
8826 }
8827
8828 /**
8829 * Returns the total height of item decorations applied to child's top.
8830 * <p>
8831 * Note that this value is not updated until the View is measured or
8832 * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
8833 *
8834 * @param child Child to query
8835 * @return The total height of item decorations applied to the child's top.
8836 * @see #getDecoratedTop(View)
8837 * @see #calculateItemDecorationsForChild(View, Rect)
8838 */
8839 public int getTopDecorationHeight(View child) {
8840 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top;
8841 }
8842
8843 /**
8844 * Returns the total height of item decorations applied to child's bottom.
8845 * <p>
8846 * Note that this value is not updated until the View is measured or
8847 * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
8848 *
8849 * @param child Child to query
8850 * @return The total height of item decorations applied to the child's bottom.
8851 * @see #getDecoratedBottom(View)
8852 * @see #calculateItemDecorationsForChild(View, Rect)
8853 */
8854 public int getBottomDecorationHeight(View child) {
8855 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom;
8856 }
8857
8858 /**
8859 * Returns the total width of item decorations applied to child's left.
8860 * <p>
8861 * Note that this value is not updated until the View is measured or
8862 * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
8863 *
8864 * @param child Child to query
8865 * @return The total width of item decorations applied to the child's left.
8866 * @see #getDecoratedLeft(View)
8867 * @see #calculateItemDecorationsForChild(View, Rect)
8868 */
8869 public int getLeftDecorationWidth(View child) {
8870 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left;
8871 }
8872
8873 /**
8874 * Returns the total width of item decorations applied to child's right.
8875 * <p>
8876 * Note that this value is not updated until the View is measured or
8877 * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
8878 *
8879 * @param child Child to query
8880 * @return The total width of item decorations applied to the child's right.
8881 * @see #getDecoratedRight(View)
8882 * @see #calculateItemDecorationsForChild(View, Rect)
8883 */
8884 public int getRightDecorationWidth(View child) {
8885 return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right;
8886 }
8887
8888 /**
8889 * Called when searching for a focusable view in the given direction has failed
8890 * for the current content of the RecyclerView.
8891 *
8892 * <p>This is the LayoutManager's opportunity to populate views in the given direction
8893 * to fulfill the request if it can. The LayoutManager should attach and return
8894 * the view to be focused. The default implementation returns null.</p>
8895 *
8896 * @param focused The currently focused view
8897 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
8898 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
8899 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
8900 * or 0 for not applicable
8901 * @param recycler The recycler to use for obtaining views for currently offscreen items
8902 * @param state Transient state of RecyclerView
8903 * @return The chosen view to be focused
8904 */
8905 @Nullable
8906 public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
8907 State state) {
8908 return null;
8909 }
8910
8911 /**
8912 * This method gives a LayoutManager an opportunity to intercept the initial focus search
8913 * before the default behavior of {@link FocusFinder} is used. If this method returns
8914 * null FocusFinder will attempt to find a focusable child view. If it fails
8915 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
8916 * will be called to give the LayoutManager an opportunity to add new views for items
8917 * that did not have attached views representing them. The LayoutManager should not add
8918 * or remove views from this method.
8919 *
8920 * @param focused The currently focused view
8921 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
8922 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
8923 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
8924 * @return A descendant view to focus or null to fall back to default behavior.
8925 * The default implementation returns null.
8926 */
8927 public View onInterceptFocusSearch(View focused, int direction) {
8928 return null;
8929 }
8930
8931 /**
8932 * Called when a child of the RecyclerView wants a particular rectangle to be positioned
8933 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
8934 * android.graphics.Rect, boolean)} for more details.
8935 *
8936 * <p>The base implementation will attempt to perform a standard programmatic scroll
8937 * to bring the given rect into view, within the padded area of the RecyclerView.</p>
8938 *
8939 * @param child The direct child making the request.
8940 * @param rect The rectangle in the child's coordinates the child
8941 * wishes to be on the screen.
8942 * @param immediate True to forbid animated or delayed scrolling,
8943 * false otherwise
8944 * @return Whether the group scrolled to handle the operation
8945 */
8946 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
8947 boolean immediate) {
8948 final int parentLeft = getPaddingLeft();
8949 final int parentTop = getPaddingTop();
8950 final int parentRight = getWidth() - getPaddingRight();
8951 final int parentBottom = getHeight() - getPaddingBottom();
8952 final int childLeft = child.getLeft() + rect.left - child.getScrollX();
8953 final int childTop = child.getTop() + rect.top - child.getScrollY();
8954 final int childRight = childLeft + rect.width();
8955 final int childBottom = childTop + rect.height();
8956
8957 final int offScreenLeft = Math.min(0, childLeft - parentLeft);
8958 final int offScreenTop = Math.min(0, childTop - parentTop);
8959 final int offScreenRight = Math.max(0, childRight - parentRight);
8960 final int offScreenBottom = Math.max(0, childBottom - parentBottom);
8961
8962 // Favor the "start" layout direction over the end when bringing one side or the other
8963 // of a large rect into view. If we decide to bring in end because start is already
8964 // visible, limit the scroll such that start won't go out of bounds.
8965 final int dx;
8966 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
8967 dx = offScreenRight != 0 ? offScreenRight
8968 : Math.max(offScreenLeft, childRight - parentRight);
8969 } else {
8970 dx = offScreenLeft != 0 ? offScreenLeft
8971 : Math.min(childLeft - parentLeft, offScreenRight);
8972 }
8973
8974 // Favor bringing the top into view over the bottom. If top is already visible and
8975 // we should scroll to make bottom visible, make sure top does not go out of bounds.
8976 final int dy = offScreenTop != 0 ? offScreenTop
8977 : Math.min(childTop - parentTop, offScreenBottom);
8978
8979 if (dx != 0 || dy != 0) {
8980 if (immediate) {
8981 parent.scrollBy(dx, dy);
8982 } else {
8983 parent.smoothScrollBy(dx, dy);
8984 }
8985 return true;
8986 }
8987 return false;
8988 }
8989
8990 /**
8991 * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)}
8992 */
8993 @Deprecated
8994 public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
8995 // eat the request if we are in the middle of a scroll or layout
8996 return isSmoothScrolling() || parent.isComputingLayout();
8997 }
8998
8999 /**
9000 * Called when a descendant view of the RecyclerView requests focus.
9001 *
9002 * <p>A LayoutManager wishing to keep focused views aligned in a specific
9003 * portion of the view may implement that behavior in an override of this method.</p>
9004 *
9005 * <p>If the LayoutManager executes different behavior that should override the default
9006 * behavior of scrolling the focused child on screen instead of running alongside it,
9007 * this method should return true.</p>
9008 *
9009 * @param parent The RecyclerView hosting this LayoutManager
9010 * @param state Current state of RecyclerView
9011 * @param child Direct child of the RecyclerView containing the newly focused view
9012 * @param focused The newly focused view. This may be the same view as child or it may be
9013 * null
9014 * @return true if the default scroll behavior should be suppressed
9015 */
9016 public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
9017 View focused) {
9018 return onRequestChildFocus(parent, child, focused);
9019 }
9020
9021 /**
9022 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
9023 * The LayoutManager may use this opportunity to clear caches and configure state such
9024 * that it can relayout appropriately with the new data and potentially new view types.
9025 *
9026 * <p>The default implementation removes all currently attached views.</p>
9027 *
9028 * @param oldAdapter The previous adapter instance. Will be null if there was previously no
9029 * adapter.
9030 * @param newAdapter The new adapter instance. Might be null if
9031 * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
9032 */
9033 public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
9034 }
9035
9036 /**
9037 * Called to populate focusable views within the RecyclerView.
9038 *
9039 * <p>The LayoutManager implementation should return <code>true</code> if the default
9040 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be
9041 * suppressed.</p>
9042 *
9043 * <p>The default implementation returns <code>false</code> to trigger RecyclerView
9044 * to fall back to the default ViewGroup behavior.</p>
9045 *
9046 * @param recyclerView The RecyclerView hosting this LayoutManager
9047 * @param views List of output views. This method should add valid focusable views
9048 * to this list.
9049 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
9050 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
9051 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
9052 * @param focusableMode The type of focusables to be added.
9053 *
9054 * @return true to suppress the default behavior, false to add default focusables after
9055 * this method returns.
9056 *
9057 * @see #FOCUSABLES_ALL
9058 * @see #FOCUSABLES_TOUCH_MODE
9059 */
9060 public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
9061 int direction, int focusableMode) {
9062 return false;
9063 }
9064
9065 /**
9066 * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
9067 * detailed information on what has actually changed.
9068 *
9069 * @param recyclerView
9070 */
9071 public void onItemsChanged(RecyclerView recyclerView) {
9072 }
9073
9074 /**
9075 * Called when items have been added to the adapter. The LayoutManager may choose to
9076 * requestLayout if the inserted items would require refreshing the currently visible set
9077 * of child views. (e.g. currently empty space would be filled by appended items, etc.)
9078 *
9079 * @param recyclerView
9080 * @param positionStart
9081 * @param itemCount
9082 */
9083 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
9084 }
9085
9086 /**
9087 * Called when items have been removed from the adapter.
9088 *
9089 * @param recyclerView
9090 * @param positionStart
9091 * @param itemCount
9092 */
9093 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
9094 }
9095
9096 /**
9097 * Called when items have been changed in the adapter.
9098 * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)}
9099 * instead, then this callback will not be invoked.
9100 *
9101 * @param recyclerView
9102 * @param positionStart
9103 * @param itemCount
9104 */
9105 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
9106 }
9107
9108 /**
9109 * Called when items have been changed in the adapter and with optional payload.
9110 * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}.
9111 *
9112 * @param recyclerView
9113 * @param positionStart
9114 * @param itemCount
9115 * @param payload
9116 */
9117 public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
9118 Object payload) {
9119 onItemsUpdated(recyclerView, positionStart, itemCount);
9120 }
9121
9122 /**
9123 * Called when an item is moved withing the adapter.
9124 * <p>
9125 * Note that, an item may also change position in response to another ADD/REMOVE/MOVE
9126 * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved}
9127 * is called.
9128 *
9129 * @param recyclerView
9130 * @param from
9131 * @param to
9132 * @param itemCount
9133 */
9134 public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
9135
9136 }
9137
9138
9139 /**
9140 * <p>Override this method if you want to support scroll bars.</p>
9141 *
9142 * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
9143 *
9144 * <p>Default implementation returns 0.</p>
9145 *
9146 * @param state Current state of RecyclerView
9147 * @return The horizontal extent of the scrollbar's thumb
9148 * @see RecyclerView#computeHorizontalScrollExtent()
9149 */
9150 public int computeHorizontalScrollExtent(State state) {
9151 return 0;
9152 }
9153
9154 /**
9155 * <p>Override this method if you want to support scroll bars.</p>
9156 *
9157 * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
9158 *
9159 * <p>Default implementation returns 0.</p>
9160 *
9161 * @param state Current State of RecyclerView where you can find total item count
9162 * @return The horizontal offset of the scrollbar's thumb
9163 * @see RecyclerView#computeHorizontalScrollOffset()
9164 */
9165 public int computeHorizontalScrollOffset(State state) {
9166 return 0;
9167 }
9168
9169 /**
9170 * <p>Override this method if you want to support scroll bars.</p>
9171 *
9172 * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
9173 *
9174 * <p>Default implementation returns 0.</p>
9175 *
9176 * @param state Current State of RecyclerView where you can find total item count
9177 * @return The total horizontal range represented by the vertical scrollbar
9178 * @see RecyclerView#computeHorizontalScrollRange()
9179 */
9180 public int computeHorizontalScrollRange(State state) {
9181 return 0;
9182 }
9183
9184 /**
9185 * <p>Override this method if you want to support scroll bars.</p>
9186 *
9187 * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
9188 *
9189 * <p>Default implementation returns 0.</p>
9190 *
9191 * @param state Current state of RecyclerView
9192 * @return The vertical extent of the scrollbar's thumb
9193 * @see RecyclerView#computeVerticalScrollExtent()
9194 */
9195 public int computeVerticalScrollExtent(State state) {
9196 return 0;
9197 }
9198
9199 /**
9200 * <p>Override this method if you want to support scroll bars.</p>
9201 *
9202 * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
9203 *
9204 * <p>Default implementation returns 0.</p>
9205 *
9206 * @param state Current State of RecyclerView where you can find total item count
9207 * @return The vertical offset of the scrollbar's thumb
9208 * @see RecyclerView#computeVerticalScrollOffset()
9209 */
9210 public int computeVerticalScrollOffset(State state) {
9211 return 0;
9212 }
9213
9214 /**
9215 * <p>Override this method if you want to support scroll bars.</p>
9216 *
9217 * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
9218 *
9219 * <p>Default implementation returns 0.</p>
9220 *
9221 * @param state Current State of RecyclerView where you can find total item count
9222 * @return The total vertical range represented by the vertical scrollbar
9223 * @see RecyclerView#computeVerticalScrollRange()
9224 */
9225 public int computeVerticalScrollRange(State state) {
9226 return 0;
9227 }
9228
9229 /**
9230 * Measure the attached RecyclerView. Implementations must call
9231 * {@link #setMeasuredDimension(int, int)} before returning.
9232 *
9233 * <p>The default implementation will handle EXACTLY measurements and respect
9234 * the minimum width and height properties of the host RecyclerView if measured
9235 * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
9236 * will consume all available space.</p>
9237 *
9238 * @param recycler Recycler
9239 * @param state Transient state of RecyclerView
9240 * @param widthSpec Width {@link android.view.View.MeasureSpec}
9241 * @param heightSpec Height {@link android.view.View.MeasureSpec}
9242 */
9243 public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
9244 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
9245 }
9246
9247 /**
9248 * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the
9249 * host RecyclerView.
9250 *
9251 * @param widthSize Measured width
9252 * @param heightSize Measured height
9253 */
9254 public void setMeasuredDimension(int widthSize, int heightSize) {
9255 mRecyclerView.setMeasuredDimension(widthSize, heightSize);
9256 }
9257
9258 /**
9259 * @return The host RecyclerView's {@link View#getMinimumWidth()}
9260 */
9261 public int getMinimumWidth() {
9262 return mRecyclerView.getMinimumWidth();
9263 }
9264
9265 /**
9266 * @return The host RecyclerView's {@link View#getMinimumHeight()}
9267 */
9268 public int getMinimumHeight() {
9269 return mRecyclerView.getMinimumHeight();
9270 }
9271 /**
9272 * <p>Called when the LayoutManager should save its state. This is a good time to save your
9273 * scroll position, configuration and anything else that may be required to restore the same
9274 * layout state if the LayoutManager is recreated.</p>
9275 * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and
9276 * restore. This will let you share information between your LayoutManagers but it is also
9277 * your responsibility to make sure they use the same parcelable class.</p>
9278 *
9279 * @return Necessary information for LayoutManager to be able to restore its state
9280 */
9281 public Parcelable onSaveInstanceState() {
9282 return null;
9283 }
9284
9285
9286 public void onRestoreInstanceState(Parcelable state) {
9287
9288 }
9289
9290 void stopSmoothScroller() {
9291 if (mSmoothScroller != null) {
9292 mSmoothScroller.stop();
9293 }
9294 }
9295
9296 private void onSmoothScrollerStopped(SmoothScroller smoothScroller) {
9297 if (mSmoothScroller == smoothScroller) {
9298 mSmoothScroller = null;
9299 }
9300 }
9301
9302 /**
9303 * RecyclerView calls this method to notify LayoutManager that scroll state has changed.
9304 *
9305 * @param state The new scroll state for RecyclerView
9306 */
9307 public void onScrollStateChanged(int state) {
9308 }
9309
9310 /**
9311 * Removes all views and recycles them using the given recycler.
9312 * <p>
9313 * If you want to clean cached views as well, you should call {@link Recycler#clear()} too.
9314 * <p>
9315 * If a View is marked as "ignored", it is not removed nor recycled.
9316 *
9317 * @param recycler Recycler to use to recycle children
9318 * @see #removeAndRecycleView(View, Recycler)
9319 * @see #removeAndRecycleViewAt(int, Recycler)
9320 * @see #ignoreView(View)
9321 */
9322 public void removeAndRecycleAllViews(Recycler recycler) {
9323 for (int i = getChildCount() - 1; i >= 0; i--) {
9324 final View view = getChildAt(i);
9325 if (!getChildViewHolderInt(view).shouldIgnore()) {
9326 removeAndRecycleViewAt(i, recycler);
9327 }
9328 }
9329 }
9330
9331 // called by accessibility delegate
9332 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
9333 onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info);
9334 }
9335
9336 /**
9337 * Called by the AccessibilityDelegate when the information about the current layout should
9338 * be populated.
9339 * <p>
9340 * Default implementation adds a {@link
9341 * android.view.accessibility.AccessibilityNodeInfo.CollectionInfo}.
9342 * <p>
9343 * You should override
9344 * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
9345 * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
9346 * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and
9347 * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for
9348 * more accurate accessibility information.
9349 *
9350 * @param recycler The Recycler that can be used to convert view positions into adapter
9351 * positions
9352 * @param state The current state of RecyclerView
9353 * @param info The info that should be filled by the LayoutManager
9354 * @see View#onInitializeAccessibilityNodeInfo(
9355 *android.view.accessibility.AccessibilityNodeInfo)
9356 * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
9357 * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
9358 * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)
9359 * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)
9360 */
9361 public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
9362 AccessibilityNodeInfo info) {
9363 if (mRecyclerView.canScrollVertically(-1)
9364 || mRecyclerView.canScrollHorizontally(-1)) {
9365 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
9366 info.setScrollable(true);
9367 }
9368 if (mRecyclerView.canScrollVertically(1)
9369 || mRecyclerView.canScrollHorizontally(1)) {
9370 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
9371 info.setScrollable(true);
9372 }
9373 final AccessibilityNodeInfo.CollectionInfo collectionInfo =
9374 AccessibilityNodeInfo.CollectionInfo
9375 .obtain(getRowCountForAccessibility(recycler, state),
9376 getColumnCountForAccessibility(recycler, state),
9377 isLayoutHierarchical(recycler, state),
9378 getSelectionModeForAccessibility(recycler, state));
9379 info.setCollectionInfo(collectionInfo);
9380 }
9381
9382 // called by accessibility delegate
9383 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
9384 onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event);
9385 }
9386
9387 /**
9388 * Called by the accessibility delegate to initialize an accessibility event.
9389 * <p>
9390 * Default implementation adds item count and scroll information to the event.
9391 *
9392 * @param recycler The Recycler that can be used to convert view positions into adapter
9393 * positions
9394 * @param state The current state of RecyclerView
9395 * @param event The event instance to initialize
9396 * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent)
9397 */
9398 public void onInitializeAccessibilityEvent(Recycler recycler, State state,
9399 AccessibilityEvent event) {
9400 if (mRecyclerView == null || event == null) {
9401 return;
9402 }
9403 event.setScrollable(mRecyclerView.canScrollVertically(1)
9404 || mRecyclerView.canScrollVertically(-1)
9405 || mRecyclerView.canScrollHorizontally(-1)
9406 || mRecyclerView.canScrollHorizontally(1));
9407
9408 if (mRecyclerView.mAdapter != null) {
9409 event.setItemCount(mRecyclerView.mAdapter.getItemCount());
9410 }
9411 }
9412
9413 // called by accessibility delegate
9414 void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfo info) {
9415 final ViewHolder vh = getChildViewHolderInt(host);
9416 // avoid trying to create accessibility node info for removed children
9417 if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) {
9418 onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler,
9419 mRecyclerView.mState, host, info);
9420 }
9421 }
9422
9423 /**
9424 * Called by the AccessibilityDelegate when the accessibility information for a specific
9425 * item should be populated.
9426 * <p>
9427 * Default implementation adds basic positioning information about the item.
9428 *
9429 * @param recycler The Recycler that can be used to convert view positions into adapter
9430 * positions
9431 * @param state The current state of RecyclerView
9432 * @param host The child for which accessibility node info should be populated
9433 * @param info The info to fill out about the item
9434 * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int,
9435 * android.view.accessibility.AccessibilityNodeInfo)
9436 */
9437 public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state,
9438 View host, AccessibilityNodeInfo info) {
9439 int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0;
9440 int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0;
9441 final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
9442 AccessibilityNodeInfo.CollectionItemInfo.obtain(rowIndexGuess, 1,
9443 columnIndexGuess, 1, false, false);
9444 info.setCollectionItemInfo(itemInfo);
9445 }
9446
9447 /**
9448 * A LayoutManager can call this method to force RecyclerView to run simple animations in
9449 * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data
9450 * change).
9451 * <p>
9452 * Note that, calling this method will not guarantee that RecyclerView will run animations
9453 * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will
9454 * not run any animations but will still clear this flag after the layout is complete.
9455 *
9456 */
9457 public void requestSimpleAnimationsInNextLayout() {
9458 mRequestedSimpleAnimations = true;
9459 }
9460
9461 /**
9462 * Returns the selection mode for accessibility. Should be
9463 * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE},
9464 * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_SINGLE} or
9465 * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_MULTIPLE}.
9466 * <p>
9467 * Default implementation returns
9468 * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
9469 *
9470 * @param recycler The Recycler that can be used to convert view positions into adapter
9471 * positions
9472 * @param state The current state of RecyclerView
9473 * @return Selection mode for accessibility. Default implementation returns
9474 * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
9475 */
9476 public int getSelectionModeForAccessibility(Recycler recycler, State state) {
9477 return AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE;
9478 }
9479
9480 /**
9481 * Returns the number of rows for accessibility.
9482 * <p>
9483 * Default implementation returns the number of items in the adapter if LayoutManager
9484 * supports vertical scrolling or 1 if LayoutManager does not support vertical
9485 * scrolling.
9486 *
9487 * @param recycler The Recycler that can be used to convert view positions into adapter
9488 * positions
9489 * @param state The current state of RecyclerView
9490 * @return The number of rows in LayoutManager for accessibility.
9491 */
9492 public int getRowCountForAccessibility(Recycler recycler, State state) {
9493 if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
9494 return 1;
9495 }
9496 return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1;
9497 }
9498
9499 /**
9500 * Returns the number of columns for accessibility.
9501 * <p>
9502 * Default implementation returns the number of items in the adapter if LayoutManager
9503 * supports horizontal scrolling or 1 if LayoutManager does not support horizontal
9504 * scrolling.
9505 *
9506 * @param recycler The Recycler that can be used to convert view positions into adapter
9507 * positions
9508 * @param state The current state of RecyclerView
9509 * @return The number of rows in LayoutManager for accessibility.
9510 */
9511 public int getColumnCountForAccessibility(Recycler recycler, State state) {
9512 if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
9513 return 1;
9514 }
9515 return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1;
9516 }
9517
9518 /**
9519 * Returns whether layout is hierarchical or not to be used for accessibility.
9520 * <p>
9521 * Default implementation returns false.
9522 *
9523 * @param recycler The Recycler that can be used to convert view positions into adapter
9524 * positions
9525 * @param state The current state of RecyclerView
9526 * @return True if layout is hierarchical.
9527 */
9528 public boolean isLayoutHierarchical(Recycler recycler, State state) {
9529 return false;
9530 }
9531
9532 // called by accessibility delegate
9533 boolean performAccessibilityAction(int action, Bundle args) {
9534 return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState,
9535 action, args);
9536 }
9537
9538 /**
9539 * Called by AccessibilityDelegate when an action is requested from the RecyclerView.
9540 *
9541 * @param recycler The Recycler that can be used to convert view positions into adapter
9542 * positions
9543 * @param state The current state of RecyclerView
9544 * @param action The action to perform
9545 * @param args Optional action arguments
9546 * @see View#performAccessibilityAction(int, android.os.Bundle)
9547 */
9548 public boolean performAccessibilityAction(Recycler recycler, State state, int action,
9549 Bundle args) {
9550 if (mRecyclerView == null) {
9551 return false;
9552 }
9553 int vScroll = 0, hScroll = 0;
9554 switch (action) {
9555 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
9556 if (mRecyclerView.canScrollVertically(-1)) {
9557 vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom());
9558 }
9559 if (mRecyclerView.canScrollHorizontally(-1)) {
9560 hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight());
9561 }
9562 break;
9563 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
9564 if (mRecyclerView.canScrollVertically(1)) {
9565 vScroll = getHeight() - getPaddingTop() - getPaddingBottom();
9566 }
9567 if (mRecyclerView.canScrollHorizontally(1)) {
9568 hScroll = getWidth() - getPaddingLeft() - getPaddingRight();
9569 }
9570 break;
9571 }
9572 if (vScroll == 0 && hScroll == 0) {
9573 return false;
9574 }
Eugene Susla38343bc2017-12-20 10:43:41 -08009575 mRecyclerView.smoothScrollBy(hScroll, vScroll);
Aurimas Liutikas7149a632017-01-18 17:36:10 -08009576 return true;
9577 }
9578
9579 // called by accessibility delegate
9580 boolean performAccessibilityActionForItem(View view, int action, Bundle args) {
9581 return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState,
9582 view, action, args);
9583 }
9584
9585 /**
9586 * Called by AccessibilityDelegate when an accessibility action is requested on one of the
9587 * children of LayoutManager.
9588 * <p>
9589 * Default implementation does not do anything.
9590 *
9591 * @param recycler The Recycler that can be used to convert view positions into adapter
9592 * positions
9593 * @param state The current state of RecyclerView
9594 * @param view The child view on which the action is performed
9595 * @param action The action to perform
9596 * @param args Optional action arguments
9597 * @return true if action is handled
9598 * @see View#performAccessibilityAction(int, android.os.Bundle)
9599 */
9600 public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view,
9601 int action, Bundle args) {
9602 return false;
9603 }
9604
9605 /**
9606 * Parse the xml attributes to get the most common properties used by layout managers.
9607 *
Aurimas Liutikas7149a632017-01-18 17:36:10 -08009608 * @return an object containing the properties as specified in the attrs.
9609 */
9610 public static Properties getProperties(Context context, AttributeSet attrs,
9611 int defStyleAttr, int defStyleRes) {
9612 Properties properties = new Properties();
9613 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
9614 defStyleAttr, defStyleRes);
9615 properties.orientation = a.getInt(R.styleable.RecyclerView_orientation, VERTICAL);
9616 properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1);
9617 properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false);
9618 properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false);
9619 a.recycle();
9620 return properties;
9621 }
9622
9623 void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
9624 setMeasureSpecs(
9625 MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
9626 MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
9627 );
9628 }
9629
9630 /**
9631 * Internal API to allow LayoutManagers to be measured twice.
9632 * <p>
9633 * This is not public because LayoutManagers should be able to handle their layouts in one
9634 * pass but it is very convenient to make existing LayoutManagers support wrapping content
9635 * when both orientations are undefined.
9636 * <p>
9637 * This API will be removed after default LayoutManagers properly implement wrap content in
9638 * non-scroll orientation.
9639 */
9640 boolean shouldMeasureTwice() {
9641 return false;
9642 }
9643
9644 boolean hasFlexibleChildInBothOrientations() {
9645 final int childCount = getChildCount();
9646 for (int i = 0; i < childCount; i++) {
9647 final View child = getChildAt(i);
9648 final ViewGroup.LayoutParams lp = child.getLayoutParams();
9649 if (lp.width < 0 && lp.height < 0) {
9650 return true;
9651 }
9652 }
9653 return false;
9654 }
9655
9656 /**
9657 * Some general properties that a LayoutManager may want to use.
9658 */
9659 public static class Properties {
9660 /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
9661 public int orientation;
9662 /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
9663 public int spanCount;
9664 /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
9665 public boolean reverseLayout;
9666 /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
9667 public boolean stackFromEnd;
9668 }
9669 }
9670
9671 /**
9672 * An ItemDecoration allows the application to add a special drawing and layout offset
9673 * to specific item views from the adapter's data set. This can be useful for drawing dividers
9674 * between items, highlights, visual grouping boundaries and more.
9675 *
9676 * <p>All ItemDecorations are drawn in the order they were added, before the item
9677 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
9678 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
9679 * RecyclerView.State)}.</p>
9680 */
9681 public abstract static class ItemDecoration {
9682 /**
9683 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
9684 * Any content drawn by this method will be drawn before the item views are drawn,
9685 * and will thus appear underneath the views.
9686 *
9687 * @param c Canvas to draw into
9688 * @param parent RecyclerView this ItemDecoration is drawing into
9689 * @param state The current state of RecyclerView
9690 */
9691 public void onDraw(Canvas c, RecyclerView parent, State state) {
9692 onDraw(c, parent);
9693 }
9694
9695 /**
9696 * @deprecated
9697 * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
9698 */
9699 @Deprecated
9700 public void onDraw(Canvas c, RecyclerView parent) {
9701 }
9702
9703 /**
9704 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
9705 * Any content drawn by this method will be drawn after the item views are drawn
9706 * and will thus appear over the views.
9707 *
9708 * @param c Canvas to draw into
9709 * @param parent RecyclerView this ItemDecoration is drawing into
9710 * @param state The current state of RecyclerView.
9711 */
9712 public void onDrawOver(Canvas c, RecyclerView parent, State state) {
9713 onDrawOver(c, parent);
9714 }
9715
9716 /**
9717 * @deprecated
9718 * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
9719 */
9720 @Deprecated
9721 public void onDrawOver(Canvas c, RecyclerView parent) {
9722 }
9723
9724
9725 /**
9726 * @deprecated
9727 * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
9728 */
9729 @Deprecated
9730 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
9731 outRect.set(0, 0, 0, 0);
9732 }
9733
9734 /**
9735 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
9736 * the number of pixels that the item view should be inset by, similar to padding or margin.
9737 * The default implementation sets the bounds of outRect to 0 and returns.
9738 *
9739 * <p>
9740 * If this ItemDecoration does not affect the positioning of item views, it should set
9741 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
9742 * before returning.
9743 *
9744 * <p>
9745 * If you need to access Adapter for additional data, you can call
9746 * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
9747 * View.
9748 *
9749 * @param outRect Rect to receive the output.
9750 * @param view The child view to decorate
9751 * @param parent RecyclerView this ItemDecoration is decorating
9752 * @param state The current state of RecyclerView.
9753 */
9754 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
9755 getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
9756 parent);
9757 }
9758 }
9759
9760 /**
9761 * An OnItemTouchListener allows the application to intercept touch events in progress at the
9762 * view hierarchy level of the RecyclerView before those touch events are considered for
9763 * RecyclerView's own scrolling behavior.
9764 *
9765 * <p>This can be useful for applications that wish to implement various forms of gestural
9766 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
9767 * a touch interaction already in progress even if the RecyclerView is already handling that
9768 * gesture stream itself for the purposes of scrolling.</p>
9769 *
9770 * @see SimpleOnItemTouchListener
9771 */
9772 public interface OnItemTouchListener {
9773 /**
9774 * Silently observe and/or take over touch events sent to the RecyclerView
9775 * before they are handled by either the RecyclerView itself or its child views.
9776 *
9777 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
9778 * in the order in which each listener was added, before any other touch processing
9779 * by the RecyclerView itself or child views occurs.</p>
9780 *
9781 * @param e MotionEvent describing the touch event. All coordinates are in
9782 * the RecyclerView's coordinate system.
9783 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
9784 * to continue with the current behavior and continue observing future events in
9785 * the gesture.
9786 */
9787 boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
9788
9789 /**
9790 * Process a touch event as part of a gesture that was claimed by returning true from
9791 * a previous call to {@link #onInterceptTouchEvent}.
9792 *
9793 * @param e MotionEvent describing the touch event. All coordinates are in
9794 * the RecyclerView's coordinate system.
9795 */
9796 void onTouchEvent(RecyclerView rv, MotionEvent e);
9797
9798 /**
9799 * Called when a child of RecyclerView does not want RecyclerView and its ancestors to
9800 * intercept touch events with
9801 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
9802 *
9803 * @param disallowIntercept True if the child does not want the parent to
9804 * intercept touch events.
9805 * @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
9806 */
9807 void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
9808 }
9809
9810 /**
9811 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies
9812 * and default return values.
9813 * <p>
9814 * You may prefer to extend this class if you don't need to override all methods. Another
9815 * benefit of using this class is future compatibility. As the interface may change, we'll
9816 * always provide a default implementation on this class so that your code won't break when
9817 * you update to a new version of the support library.
9818 */
9819 public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
9820 @Override
9821 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
9822 return false;
9823 }
9824
9825 @Override
9826 public void onTouchEvent(RecyclerView rv, MotionEvent e) {
9827 }
9828
9829 @Override
9830 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
9831 }
9832 }
9833
9834
9835 /**
9836 * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event
9837 * has occurred on that RecyclerView.
9838 * <p>
9839 * @see RecyclerView#addOnScrollListener(OnScrollListener)
9840 * @see RecyclerView#clearOnChildAttachStateChangeListeners()
9841 *
9842 */
9843 public abstract static class OnScrollListener {
9844 /**
9845 * Callback method to be invoked when RecyclerView's scroll state changes.
9846 *
9847 * @param recyclerView The RecyclerView whose scroll state has changed.
9848 * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
9849 * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
9850 */
9851 public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
9852
9853 /**
9854 * Callback method to be invoked when the RecyclerView has been scrolled. This will be
9855 * called after the scroll has completed.
9856 * <p>
9857 * This callback will also be called if visible item range changes after a layout
9858 * calculation. In that case, dx and dy will be 0.
9859 *
9860 * @param recyclerView The RecyclerView which scrolled.
9861 * @param dx The amount of horizontal scroll.
9862 * @param dy The amount of vertical scroll.
9863 */
9864 public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
9865 }
9866
9867 /**
9868 * A RecyclerListener can be set on a RecyclerView to receive messages whenever
9869 * a view is recycled.
9870 *
9871 * @see RecyclerView#setRecyclerListener(RecyclerListener)
9872 */
9873 public interface RecyclerListener {
9874
9875 /**
9876 * This method is called whenever the view in the ViewHolder is recycled.
9877 *
9878 * RecyclerView calls this method right before clearing ViewHolder's internal data and
9879 * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
9880 * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
9881 * its adapter position.
9882 *
9883 * @param holder The ViewHolder containing the view that was recycled
9884 */
9885 void onViewRecycled(ViewHolder holder);
9886 }
9887
9888 /**
9889 * A Listener interface that can be attached to a RecylcerView to get notified
9890 * whenever a ViewHolder is attached to or detached from RecyclerView.
9891 */
9892 public interface OnChildAttachStateChangeListener {
9893
9894 /**
9895 * Called when a view is attached to the RecyclerView.
9896 *
9897 * @param view The View which is attached to the RecyclerView
9898 */
9899 void onChildViewAttachedToWindow(View view);
9900
9901 /**
9902 * Called when a view is detached from RecyclerView.
9903 *
9904 * @param view The View which is being detached from the RecyclerView
9905 */
9906 void onChildViewDetachedFromWindow(View view);
9907 }
9908
9909 /**
9910 * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
9911 *
9912 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
9913 * potentially expensive {@link View#findViewById(int)} results.</p>
9914 *
9915 * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
9916 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
9917 * their own custom ViewHolder implementations to store data that makes binding view contents
9918 * easier. Implementations should assume that individual item views will hold strong references
9919 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
9920 * strong references to extra off-screen item views for caching purposes</p>
9921 */
9922 public abstract static class ViewHolder {
9923 public final View itemView;
9924 WeakReference<RecyclerView> mNestedRecyclerView;
9925 int mPosition = NO_POSITION;
9926 int mOldPosition = NO_POSITION;
9927 long mItemId = NO_ID;
9928 int mItemViewType = INVALID_TYPE;
9929 int mPreLayoutPosition = NO_POSITION;
9930
9931 // The item that this holder is shadowing during an item change event/animation
9932 ViewHolder mShadowedHolder = null;
9933 // The item that is shadowing this holder during an item change event/animation
9934 ViewHolder mShadowingHolder = null;
9935
9936 /**
9937 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
9938 * are all valid.
9939 */
9940 static final int FLAG_BOUND = 1 << 0;
9941
9942 /**
9943 * The data this ViewHolder's view reflects is stale and needs to be rebound
9944 * by the adapter. mPosition and mItemId are consistent.
9945 */
9946 static final int FLAG_UPDATE = 1 << 1;
9947
9948 /**
9949 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
9950 * are not to be trusted and may no longer match the item view type.
9951 * This ViewHolder must be fully rebound to different data.
9952 */
9953 static final int FLAG_INVALID = 1 << 2;
9954
9955 /**
9956 * This ViewHolder points at data that represents an item previously removed from the
9957 * data set. Its view may still be used for things like outgoing animations.
9958 */
9959 static final int FLAG_REMOVED = 1 << 3;
9960
9961 /**
9962 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
9963 * and is intended to keep views around during animations.
9964 */
9965 static final int FLAG_NOT_RECYCLABLE = 1 << 4;
9966
9967 /**
9968 * This ViewHolder is returned from scrap which means we are expecting an addView call
9969 * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
9970 * the end of the layout pass and then recycled by RecyclerView if it is not added back to
9971 * the RecyclerView.
9972 */
9973 static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
9974
9975 /**
9976 * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
9977 * it unless LayoutManager is replaced.
9978 * It is still fully visible to the LayoutManager.
9979 */
9980 static final int FLAG_IGNORE = 1 << 7;
9981
9982 /**
9983 * When the View is detached form the parent, we set this flag so that we can take correct
9984 * action when we need to remove it or add it back.
9985 */
9986 static final int FLAG_TMP_DETACHED = 1 << 8;
9987
9988 /**
9989 * Set when we can no longer determine the adapter position of this ViewHolder until it is
9990 * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
9991 * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
9992 * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
9993 * re-calculated.
9994 */
9995 static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;
9996
9997 /**
9998 * Set when a addChangePayload(null) is called
9999 */
10000 static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;
10001
10002 /**
10003 * Used by ItemAnimator when a ViewHolder's position changes
10004 */
10005 static final int FLAG_MOVED = 1 << 11;
10006
10007 /**
10008 * Used by ItemAnimator when a ViewHolder appears in pre-layout
10009 */
10010 static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;
10011
10012 static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1;
10013
10014 /**
10015 * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from
10016 * hidden list (as if it was scrap) without being recycled in between.
10017 *
10018 * When a ViewHolder is hidden, there are 2 paths it can be re-used:
10019 * a) Animation ends, view is recycled and used from the recycle pool.
10020 * b) LayoutManager asks for the View for that position while the ViewHolder is hidden.
10021 *
10022 * This flag is used to represent "case b" where the ViewHolder is reused without being
10023 * recycled (thus "bounced" from the hidden list). This state requires special handling
10024 * because the ViewHolder must be added to pre layout maps for animations as if it was
10025 * already there.
10026 */
10027 static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13;
10028
10029 private int mFlags;
10030
10031 private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST;
10032
10033 List<Object> mPayloads = null;
10034 List<Object> mUnmodifiedPayloads = null;
10035
10036 private int mIsRecyclableCount = 0;
10037
10038 // If non-null, view is currently considered scrap and may be reused for other data by the
10039 // scrap container.
10040 private Recycler mScrapContainer = null;
10041 // Keeps whether this ViewHolder lives in Change scrap or Attached scrap
10042 private boolean mInChangeScrap = false;
10043
10044 // Saves isImportantForAccessibility value for the view item while it's in hidden state and
10045 // marked as unimportant for accessibility.
10046 private int mWasImportantForAccessibilityBeforeHidden =
10047 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
10048 // set if we defer the accessibility state change of the view holder
10049 @VisibleForTesting
10050 int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
10051
10052 /**
10053 * Is set when VH is bound from the adapter and cleaned right before it is sent to
10054 * {@link RecycledViewPool}.
10055 */
10056 RecyclerView mOwnerRecyclerView;
10057
10058 public ViewHolder(View itemView) {
10059 if (itemView == null) {
10060 throw new IllegalArgumentException("itemView may not be null");
10061 }
10062 this.itemView = itemView;
10063 }
10064
10065 void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
10066 addFlags(ViewHolder.FLAG_REMOVED);
10067 offsetPosition(offset, applyToPreLayout);
10068 mPosition = mNewPosition;
10069 }
10070
10071 void offsetPosition(int offset, boolean applyToPreLayout) {
10072 if (mOldPosition == NO_POSITION) {
10073 mOldPosition = mPosition;
10074 }
10075 if (mPreLayoutPosition == NO_POSITION) {
10076 mPreLayoutPosition = mPosition;
10077 }
10078 if (applyToPreLayout) {
10079 mPreLayoutPosition += offset;
10080 }
10081 mPosition += offset;
10082 if (itemView.getLayoutParams() != null) {
10083 ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
10084 }
10085 }
10086
10087 void clearOldPosition() {
10088 mOldPosition = NO_POSITION;
10089 mPreLayoutPosition = NO_POSITION;
10090 }
10091
10092 void saveOldPosition() {
10093 if (mOldPosition == NO_POSITION) {
10094 mOldPosition = mPosition;
10095 }
10096 }
10097
10098 boolean shouldIgnore() {
10099 return (mFlags & FLAG_IGNORE) != 0;
10100 }
10101
10102 /**
10103 * @deprecated This method is deprecated because its meaning is ambiguous due to the async
10104 * handling of adapter updates. Please use {@link #getLayoutPosition()} or
10105 * {@link #getAdapterPosition()} depending on your use case.
10106 *
10107 * @see #getLayoutPosition()
10108 * @see #getAdapterPosition()
10109 */
10110 @Deprecated
10111 public final int getPosition() {
10112 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
10113 }
10114
10115 /**
10116 * Returns the position of the ViewHolder in terms of the latest layout pass.
10117 * <p>
10118 * This position is mostly used by RecyclerView components to be consistent while
10119 * RecyclerView lazily processes adapter updates.
10120 * <p>
10121 * For performance and animation reasons, RecyclerView batches all adapter updates until the
10122 * next layout pass. This may cause mismatches between the Adapter position of the item and
10123 * the position it had in the latest layout calculations.
10124 * <p>
10125 * LayoutManagers should always call this method while doing calculations based on item
10126 * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
10127 * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
10128 * of the item.
10129 * <p>
10130 * If LayoutManager needs to call an external method that requires the adapter position of
10131 * the item, it can use {@link #getAdapterPosition()} or
10132 * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
10133 *
10134 * @return Returns the adapter position of the ViewHolder in the latest layout pass.
10135 * @see #getAdapterPosition()
10136 */
10137 public final int getLayoutPosition() {
10138 return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
10139 }
10140
10141 /**
10142 * Returns the Adapter position of the item represented by this ViewHolder.
10143 * <p>
10144 * Note that this might be different than the {@link #getLayoutPosition()} if there are
10145 * pending adapter updates but a new layout pass has not happened yet.
10146 * <p>
10147 * RecyclerView does not handle any adapter updates until the next layout traversal. This
10148 * may create temporary inconsistencies between what user sees on the screen and what
10149 * adapter contents have. This inconsistency is not important since it will be less than
10150 * 16ms but it might be a problem if you want to use ViewHolder position to access the
10151 * adapter. Sometimes, you may need to get the exact adapter position to do
10152 * some actions in response to user events. In that case, you should use this method which
10153 * will calculate the Adapter position of the ViewHolder.
10154 * <p>
10155 * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
10156 * next layout pass, the return value of this method will be {@link #NO_POSITION}.
10157 *
10158 * @return The adapter position of the item if it still exists in the adapter.
10159 * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
10160 * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
10161 * layout pass or the ViewHolder has already been recycled.
10162 */
10163 public final int getAdapterPosition() {
10164 if (mOwnerRecyclerView == null) {
10165 return NO_POSITION;
10166 }
10167 return mOwnerRecyclerView.getAdapterPositionFor(this);
10168 }
10169
10170 /**
10171 * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
10172 * to perform animations.
10173 * <p>
10174 * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
10175 * adapter index in the previous layout.
10176 *
10177 * @return The previous adapter index of the Item represented by this ViewHolder or
10178 * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is
10179 * complete).
10180 */
10181 public final int getOldPosition() {
10182 return mOldPosition;
10183 }
10184
10185 /**
10186 * Returns The itemId represented by this ViewHolder.
10187 *
10188 * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
10189 * otherwise
10190 */
10191 public final long getItemId() {
10192 return mItemId;
10193 }
10194
10195 /**
10196 * @return The view type of this ViewHolder.
10197 */
10198 public final int getItemViewType() {
10199 return mItemViewType;
10200 }
10201
10202 boolean isScrap() {
10203 return mScrapContainer != null;
10204 }
10205
10206 void unScrap() {
10207 mScrapContainer.unscrapView(this);
10208 }
10209
10210 boolean wasReturnedFromScrap() {
10211 return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
10212 }
10213
10214 void clearReturnedFromScrapFlag() {
10215 mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
10216 }
10217
10218 void clearTmpDetachFlag() {
10219 mFlags = mFlags & ~FLAG_TMP_DETACHED;
10220 }
10221
10222 void stopIgnoring() {
10223 mFlags = mFlags & ~FLAG_IGNORE;
10224 }
10225
10226 void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
10227 mScrapContainer = recycler;
10228 mInChangeScrap = isChangeScrap;
10229 }
10230
10231 boolean isInvalid() {
10232 return (mFlags & FLAG_INVALID) != 0;
10233 }
10234
10235 boolean needsUpdate() {
10236 return (mFlags & FLAG_UPDATE) != 0;
10237 }
10238
10239 boolean isBound() {
10240 return (mFlags & FLAG_BOUND) != 0;
10241 }
10242
10243 boolean isRemoved() {
10244 return (mFlags & FLAG_REMOVED) != 0;
10245 }
10246
10247 boolean hasAnyOfTheFlags(int flags) {
10248 return (mFlags & flags) != 0;
10249 }
10250
10251 boolean isTmpDetached() {
10252 return (mFlags & FLAG_TMP_DETACHED) != 0;
10253 }
10254
10255 boolean isAdapterPositionUnknown() {
10256 return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid();
10257 }
10258
10259 void setFlags(int flags, int mask) {
10260 mFlags = (mFlags & ~mask) | (flags & mask);
10261 }
10262
10263 void addFlags(int flags) {
10264 mFlags |= flags;
10265 }
10266
10267 void addChangePayload(Object payload) {
10268 if (payload == null) {
10269 addFlags(FLAG_ADAPTER_FULLUPDATE);
10270 } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
10271 createPayloadsIfNeeded();
10272 mPayloads.add(payload);
10273 }
10274 }
10275
10276 private void createPayloadsIfNeeded() {
10277 if (mPayloads == null) {
10278 mPayloads = new ArrayList<Object>();
10279 mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads);
10280 }
10281 }
10282
10283 void clearPayload() {
10284 if (mPayloads != null) {
10285 mPayloads.clear();
10286 }
10287 mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE;
10288 }
10289
10290 List<Object> getUnmodifiedPayloads() {
10291 if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
10292 if (mPayloads == null || mPayloads.size() == 0) {
10293 // Initial state, no update being called.
10294 return FULLUPDATE_PAYLOADS;
10295 }
10296 // there are none-null payloads
10297 return mUnmodifiedPayloads;
10298 } else {
10299 // a full update has been called.
10300 return FULLUPDATE_PAYLOADS;
10301 }
10302 }
10303
10304 void resetInternal() {
10305 mFlags = 0;
10306 mPosition = NO_POSITION;
10307 mOldPosition = NO_POSITION;
10308 mItemId = NO_ID;
10309 mPreLayoutPosition = NO_POSITION;
10310 mIsRecyclableCount = 0;
10311 mShadowedHolder = null;
10312 mShadowingHolder = null;
10313 clearPayload();
10314 mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
10315 mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
10316 clearNestedRecyclerViewIfNotNested(this);
10317 }
10318
10319 /**
10320 * Called when the child view enters the hidden state
10321 */
10322 private void onEnteredHiddenState(RecyclerView parent) {
10323 // While the view item is in hidden state, make it invisible for the accessibility.
10324 mWasImportantForAccessibilityBeforeHidden =
10325 itemView.getImportantForAccessibility();
10326 parent.setChildImportantForAccessibilityInternal(this,
10327 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
10328 }
10329
10330 /**
10331 * Called when the child view leaves the hidden state
10332 */
10333 private void onLeftHiddenState(RecyclerView parent) {
10334 parent.setChildImportantForAccessibilityInternal(this,
10335 mWasImportantForAccessibilityBeforeHidden);
10336 mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
10337 }
10338
10339 @Override
10340 public String toString() {
10341 final StringBuilder sb = new StringBuilder("ViewHolder{"
10342 + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
10343 + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
10344 if (isScrap()) {
10345 sb.append(" scrap ")
10346 .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
10347 }
10348 if (isInvalid()) sb.append(" invalid");
10349 if (!isBound()) sb.append(" unbound");
10350 if (needsUpdate()) sb.append(" update");
10351 if (isRemoved()) sb.append(" removed");
10352 if (shouldIgnore()) sb.append(" ignored");
10353 if (isTmpDetached()) sb.append(" tmpDetached");
10354 if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
10355 if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");
10356
10357 if (itemView.getParent() == null) sb.append(" no parent");
10358 sb.append("}");
10359 return sb.toString();
10360 }
10361
10362 /**
10363 * Informs the recycler whether this item can be recycled. Views which are not
10364 * recyclable will not be reused for other items until setIsRecyclable() is
10365 * later set to true. Calls to setIsRecyclable() should always be paired (one
10366 * call to setIsRecyclabe(false) should always be matched with a later call to
10367 * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
10368 * reference-counted.
10369 *
10370 * @param recyclable Whether this item is available to be recycled. Default value
10371 * is true.
10372 *
10373 * @see #isRecyclable()
10374 */
10375 public final void setIsRecyclable(boolean recyclable) {
10376 mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
10377 if (mIsRecyclableCount < 0) {
10378 mIsRecyclableCount = 0;
10379 if (DEBUG) {
10380 throw new RuntimeException("isRecyclable decremented below 0: "
10381 + "unmatched pair of setIsRecyable() calls for " + this);
10382 }
10383 Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: "
10384 + "unmatched pair of setIsRecyable() calls for " + this);
10385 } else if (!recyclable && mIsRecyclableCount == 1) {
10386 mFlags |= FLAG_NOT_RECYCLABLE;
10387 } else if (recyclable && mIsRecyclableCount == 0) {
10388 mFlags &= ~FLAG_NOT_RECYCLABLE;
10389 }
10390 if (DEBUG) {
10391 Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
10392 }
10393 }
10394
10395 /**
10396 * @return true if this item is available to be recycled, false otherwise.
10397 *
10398 * @see #setIsRecyclable(boolean)
10399 */
10400 public final boolean isRecyclable() {
10401 return (mFlags & FLAG_NOT_RECYCLABLE) == 0
10402 && !itemView.hasTransientState();
10403 }
10404
10405 /**
10406 * Returns whether we have animations referring to this view holder or not.
10407 * This is similar to isRecyclable flag but does not check transient state.
10408 */
10409 private boolean shouldBeKeptAsChild() {
10410 return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
10411 }
10412
10413 /**
10414 * @return True if ViewHolder is not referenced by RecyclerView animations but has
10415 * transient state which will prevent it from being recycled.
10416 */
10417 private boolean doesTransientStatePreventRecycling() {
10418 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && itemView.hasTransientState();
10419 }
10420
10421 boolean isUpdated() {
10422 return (mFlags & FLAG_UPDATE) != 0;
10423 }
10424 }
10425
10426 /**
10427 * This method is here so that we can control the important for a11y changes and test it.
10428 */
10429 @VisibleForTesting
10430 boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder,
10431 int importantForAccessibility) {
10432 if (isComputingLayout()) {
10433 viewHolder.mPendingAccessibilityState = importantForAccessibility;
10434 mPendingAccessibilityImportanceChange.add(viewHolder);
10435 return false;
10436 }
10437 viewHolder.itemView.setImportantForAccessibility(importantForAccessibility);
10438 return true;
10439 }
10440
10441 void dispatchPendingImportantForAccessibilityChanges() {
10442 for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) {
10443 ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i);
10444 if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) {
10445 continue;
10446 }
10447 int state = viewHolder.mPendingAccessibilityState;
10448 if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) {
10449 //noinspection WrongConstant
10450 viewHolder.itemView.setImportantForAccessibility(state);
10451 viewHolder.mPendingAccessibilityState =
10452 ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET;
10453 }
10454 }
10455 mPendingAccessibilityImportanceChange.clear();
10456 }
10457
10458 int getAdapterPositionFor(ViewHolder viewHolder) {
10459 if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
10460 | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
10461 || !viewHolder.isBound()) {
10462 return RecyclerView.NO_POSITION;
10463 }
10464 return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
10465 }
10466
10467 /**
10468 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
10469 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
10470 * to create their own subclass of this <code>LayoutParams</code> class
10471 * to store any additional required per-child view metadata about the layout.
10472 */
10473 public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
10474 ViewHolder mViewHolder;
10475 final Rect mDecorInsets = new Rect();
10476 boolean mInsetsDirty = true;
10477 // Flag is set to true if the view is bound while it is detached from RV.
10478 // In this case, we need to manually call invalidate after view is added to guarantee that
10479 // invalidation is populated through the View hierarchy
10480 boolean mPendingInvalidate = false;
10481
10482 public LayoutParams(Context c, AttributeSet attrs) {
10483 super(c, attrs);
10484 }
10485
10486 public LayoutParams(int width, int height) {
10487 super(width, height);
10488 }
10489
10490 public LayoutParams(MarginLayoutParams source) {
10491 super(source);
10492 }
10493
10494 public LayoutParams(ViewGroup.LayoutParams source) {
10495 super(source);
10496 }
10497
10498 public LayoutParams(LayoutParams source) {
10499 super((ViewGroup.LayoutParams) source);
10500 }
10501
10502 /**
10503 * Returns true if the view this LayoutParams is attached to needs to have its content
10504 * updated from the corresponding adapter.
10505 *
10506 * @return true if the view should have its content updated
10507 */
10508 public boolean viewNeedsUpdate() {
10509 return mViewHolder.needsUpdate();
10510 }
10511
10512 /**
10513 * Returns true if the view this LayoutParams is attached to is now representing
10514 * potentially invalid data. A LayoutManager should scrap/recycle it.
10515 *
10516 * @return true if the view is invalid
10517 */
10518 public boolean isViewInvalid() {
10519 return mViewHolder.isInvalid();
10520 }
10521
10522 /**
10523 * Returns true if the adapter data item corresponding to the view this LayoutParams
10524 * is attached to has been removed from the data set. A LayoutManager may choose to
10525 * treat it differently in order to animate its outgoing or disappearing state.
10526 *
10527 * @return true if the item the view corresponds to was removed from the data set
10528 */
10529 public boolean isItemRemoved() {
10530 return mViewHolder.isRemoved();
10531 }
10532
10533 /**
10534 * Returns true if the adapter data item corresponding to the view this LayoutParams
10535 * is attached to has been changed in the data set. A LayoutManager may choose to
10536 * treat it differently in order to animate its changing state.
10537 *
10538 * @return true if the item the view corresponds to was changed in the data set
10539 */
10540 public boolean isItemChanged() {
10541 return mViewHolder.isUpdated();
10542 }
10543
10544 /**
10545 * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
10546 */
10547 @Deprecated
10548 public int getViewPosition() {
10549 return mViewHolder.getPosition();
10550 }
10551
10552 /**
10553 * Returns the adapter position that the view this LayoutParams is attached to corresponds
10554 * to as of latest layout calculation.
10555 *
10556 * @return the adapter position this view as of latest layout pass
10557 */
10558 public int getViewLayoutPosition() {
10559 return mViewHolder.getLayoutPosition();
10560 }
10561
10562 /**
10563 * Returns the up-to-date adapter position that the view this LayoutParams is attached to
10564 * corresponds to.
10565 *
10566 * @return the up-to-date adapter position this view. It may return
10567 * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
10568 * its up-to-date position cannot be calculated.
10569 */
10570 public int getViewAdapterPosition() {
10571 return mViewHolder.getAdapterPosition();
10572 }
10573 }
10574
10575 /**
10576 * Observer base class for watching changes to an {@link Adapter}.
10577 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
10578 */
10579 public abstract static class AdapterDataObserver {
10580 public void onChanged() {
10581 // Do nothing
10582 }
10583
10584 public void onItemRangeChanged(int positionStart, int itemCount) {
10585 // do nothing
10586 }
10587
10588 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
10589 // fallback to onItemRangeChanged(positionStart, itemCount) if app
10590 // does not override this method.
10591 onItemRangeChanged(positionStart, itemCount);
10592 }
10593
10594 public void onItemRangeInserted(int positionStart, int itemCount) {
10595 // do nothing
10596 }
10597
10598 public void onItemRangeRemoved(int positionStart, int itemCount) {
10599 // do nothing
10600 }
10601
10602 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
10603 // do nothing
10604 }
10605 }
10606
10607 /**
10608 * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and
10609 * provides methods to trigger a programmatic scroll.</p>
10610 *
10611 * @see LinearSmoothScroller
10612 */
10613 public abstract static class SmoothScroller {
10614
10615 private int mTargetPosition = RecyclerView.NO_POSITION;
10616
10617 private RecyclerView mRecyclerView;
10618
10619 private LayoutManager mLayoutManager;
10620
10621 private boolean mPendingInitialRun;
10622
10623 private boolean mRunning;
10624
10625 private View mTargetView;
10626
10627 private final Action mRecyclingAction;
10628
10629 public SmoothScroller() {
10630 mRecyclingAction = new Action(0, 0);
10631 }
10632
10633 /**
10634 * Starts a smooth scroll for the given target position.
10635 * <p>In each animation step, {@link RecyclerView} will check
10636 * for the target view and call either
10637 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
10638 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until
10639 * SmoothScroller is stopped.</p>
10640 *
10641 * <p>Note that if RecyclerView finds the target view, it will automatically stop the
10642 * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will
10643 * stop calling SmoothScroller in each animation step.</p>
10644 */
10645 void start(RecyclerView recyclerView, LayoutManager layoutManager) {
10646 mRecyclerView = recyclerView;
10647 mLayoutManager = layoutManager;
10648 if (mTargetPosition == RecyclerView.NO_POSITION) {
10649 throw new IllegalArgumentException("Invalid target position");
10650 }
10651 mRecyclerView.mState.mTargetPosition = mTargetPosition;
10652 mRunning = true;
10653 mPendingInitialRun = true;
10654 mTargetView = findViewByPosition(getTargetPosition());
10655 onStart();
10656 mRecyclerView.mViewFlinger.postOnAnimation();
10657 }
10658
10659 public void setTargetPosition(int targetPosition) {
10660 mTargetPosition = targetPosition;
10661 }
10662
10663 /**
10664 * @return The LayoutManager to which this SmoothScroller is attached. Will return
10665 * <code>null</code> after the SmoothScroller is stopped.
10666 */
10667 @Nullable
10668 public LayoutManager getLayoutManager() {
10669 return mLayoutManager;
10670 }
10671
10672 /**
10673 * Stops running the SmoothScroller in each animation callback. Note that this does not
10674 * cancel any existing {@link Action} updated by
10675 * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
10676 * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
10677 */
10678 protected final void stop() {
10679 if (!mRunning) {
10680 return;
10681 }
10682 onStop();
10683 mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
10684 mTargetView = null;
10685 mTargetPosition = RecyclerView.NO_POSITION;
10686 mPendingInitialRun = false;
10687 mRunning = false;
10688 // trigger a cleanup
10689 mLayoutManager.onSmoothScrollerStopped(this);
10690 // clear references to avoid any potential leak by a custom smooth scroller
10691 mLayoutManager = null;
10692 mRecyclerView = null;
10693 }
10694
10695 /**
10696 * Returns true if SmoothScroller has been started but has not received the first
10697 * animation
10698 * callback yet.
10699 *
10700 * @return True if this SmoothScroller is waiting to start
10701 */
10702 public boolean isPendingInitialRun() {
10703 return mPendingInitialRun;
10704 }
10705
10706
10707 /**
10708 * @return True if SmoothScroller is currently active
10709 */
10710 public boolean isRunning() {
10711 return mRunning;
10712 }
10713
10714 /**
10715 * Returns the adapter position of the target item
10716 *
10717 * @return Adapter position of the target item or
10718 * {@link RecyclerView#NO_POSITION} if no target view is set.
10719 */
10720 public int getTargetPosition() {
10721 return mTargetPosition;
10722 }
10723
10724 private void onAnimation(int dx, int dy) {
10725 final RecyclerView recyclerView = mRecyclerView;
10726 if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
10727 stop();
10728 }
10729 mPendingInitialRun = false;
10730 if (mTargetView != null) {
10731 // verify target position
10732 if (getChildPosition(mTargetView) == mTargetPosition) {
10733 onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
10734 mRecyclingAction.runIfNecessary(recyclerView);
10735 stop();
10736 } else {
10737 Log.e(TAG, "Passed over target position while smooth scrolling.");
10738 mTargetView = null;
10739 }
10740 }
10741 if (mRunning) {
10742 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);
10743 boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();
10744 mRecyclingAction.runIfNecessary(recyclerView);
10745 if (hadJumpTarget) {
10746 // It is not stopped so needs to be restarted
10747 if (mRunning) {
10748 mPendingInitialRun = true;
10749 recyclerView.mViewFlinger.postOnAnimation();
10750 } else {
10751 stop(); // done
10752 }
10753 }
10754 }
10755 }
10756
10757 /**
10758 * @see RecyclerView#getChildLayoutPosition(android.view.View)
10759 */
10760 public int getChildPosition(View view) {
10761 return mRecyclerView.getChildLayoutPosition(view);
10762 }
10763
10764 /**
10765 * @see RecyclerView.LayoutManager#getChildCount()
10766 */
10767 public int getChildCount() {
10768 return mRecyclerView.mLayout.getChildCount();
10769 }
10770
10771 /**
10772 * @see RecyclerView.LayoutManager#findViewByPosition(int)
10773 */
10774 public View findViewByPosition(int position) {
10775 return mRecyclerView.mLayout.findViewByPosition(position);
10776 }
10777
10778 /**
10779 * @see RecyclerView#scrollToPosition(int)
10780 * @deprecated Use {@link Action#jumpTo(int)}.
10781 */
10782 @Deprecated
10783 public void instantScrollToPosition(int position) {
10784 mRecyclerView.scrollToPosition(position);
10785 }
10786
10787 protected void onChildAttachedToWindow(View child) {
10788 if (getChildPosition(child) == getTargetPosition()) {
10789 mTargetView = child;
10790 if (DEBUG) {
10791 Log.d(TAG, "smooth scroll target view has been attached");
10792 }
10793 }
10794 }
10795
10796 /**
10797 * Normalizes the vector.
10798 * @param scrollVector The vector that points to the target scroll position
10799 */
10800 protected void normalize(PointF scrollVector) {
10801 final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y
10802 * scrollVector.y);
10803 scrollVector.x /= magnitude;
10804 scrollVector.y /= magnitude;
10805 }
10806
10807 /**
10808 * Called when smooth scroll is started. This might be a good time to do setup.
10809 */
10810 protected abstract void onStart();
10811
10812 /**
10813 * Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
10814 * @see #stop()
10815 */
10816 protected abstract void onStop();
10817
10818 /**
10819 * <p>RecyclerView will call this method each time it scrolls until it can find the target
10820 * position in the layout.</p>
10821 * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the
10822 * provided {@link Action} to define the next scroll.</p>
10823 *
10824 * @param dx Last scroll amount horizontally
10825 * @param dy Last scroll amount vertically
10826 * @param state Transient state of RecyclerView
10827 * @param action If you want to trigger a new smooth scroll and cancel the previous one,
10828 * update this object.
10829 */
10830 protected abstract void onSeekTargetStep(int dx, int dy, State state, Action action);
10831
10832 /**
10833 * Called when the target position is laid out. This is the last callback SmoothScroller
10834 * will receive and it should update the provided {@link Action} to define the scroll
10835 * details towards the target view.
10836 * @param targetView The view element which render the target position.
10837 * @param state Transient state of RecyclerView
10838 * @param action Action instance that you should update to define final scroll action
10839 * towards the targetView
10840 */
10841 protected abstract void onTargetFound(View targetView, State state, Action action);
10842
10843 /**
10844 * Holds information about a smooth scroll request by a {@link SmoothScroller}.
10845 */
10846 public static class Action {
10847
10848 public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
10849
10850 private int mDx;
10851
10852 private int mDy;
10853
10854 private int mDuration;
10855
10856 private int mJumpToPosition = NO_POSITION;
10857
10858 private Interpolator mInterpolator;
10859
10860 private boolean mChanged = false;
10861
10862 // we track this variable to inform custom implementer if they are updating the action
10863 // in every animation callback
10864 private int mConsecutiveUpdates = 0;
10865
10866 /**
10867 * @param dx Pixels to scroll horizontally
10868 * @param dy Pixels to scroll vertically
10869 */
10870 public Action(int dx, int dy) {
10871 this(dx, dy, UNDEFINED_DURATION, null);
10872 }
10873
10874 /**
10875 * @param dx Pixels to scroll horizontally
10876 * @param dy Pixels to scroll vertically
10877 * @param duration Duration of the animation in milliseconds
10878 */
10879 public Action(int dx, int dy, int duration) {
10880 this(dx, dy, duration, null);
10881 }
10882
10883 /**
10884 * @param dx Pixels to scroll horizontally
10885 * @param dy Pixels to scroll vertically
10886 * @param duration Duration of the animation in milliseconds
10887 * @param interpolator Interpolator to be used when calculating scroll position in each
10888 * animation step
10889 */
10890 public Action(int dx, int dy, int duration, Interpolator interpolator) {
10891 mDx = dx;
10892 mDy = dy;
10893 mDuration = duration;
10894 mInterpolator = interpolator;
10895 }
10896
10897 /**
10898 * Instead of specifying pixels to scroll, use the target position to jump using
10899 * {@link RecyclerView#scrollToPosition(int)}.
10900 * <p>
10901 * You may prefer using this method if scroll target is really far away and you prefer
10902 * to jump to a location and smooth scroll afterwards.
10903 * <p>
10904 * Note that calling this method takes priority over other update methods such as
10905 * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)},
10906 * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call
10907 * {@link #jumpTo(int)}, the other changes will not be considered for this animation
10908 * frame.
10909 *
10910 * @param targetPosition The target item position to scroll to using instant scrolling.
10911 */
10912 public void jumpTo(int targetPosition) {
10913 mJumpToPosition = targetPosition;
10914 }
10915
10916 boolean hasJumpTarget() {
10917 return mJumpToPosition >= 0;
10918 }
10919
10920 void runIfNecessary(RecyclerView recyclerView) {
10921 if (mJumpToPosition >= 0) {
10922 final int position = mJumpToPosition;
10923 mJumpToPosition = NO_POSITION;
10924 recyclerView.jumpToPositionForSmoothScroller(position);
10925 mChanged = false;
10926 return;
10927 }
10928 if (mChanged) {
10929 validate();
10930 if (mInterpolator == null) {
10931 if (mDuration == UNDEFINED_DURATION) {
10932 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
10933 } else {
10934 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
10935 }
10936 } else {
10937 recyclerView.mViewFlinger.smoothScrollBy(
10938 mDx, mDy, mDuration, mInterpolator);
10939 }
10940 mConsecutiveUpdates++;
10941 if (mConsecutiveUpdates > 10) {
10942 // A new action is being set in every animation step. This looks like a bad
10943 // implementation. Inform developer.
10944 Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure"
10945 + " you are not changing it unless necessary");
10946 }
10947 mChanged = false;
10948 } else {
10949 mConsecutiveUpdates = 0;
10950 }
10951 }
10952
10953 private void validate() {
10954 if (mInterpolator != null && mDuration < 1) {
10955 throw new IllegalStateException("If you provide an interpolator, you must"
10956 + " set a positive duration");
10957 } else if (mDuration < 1) {
10958 throw new IllegalStateException("Scroll duration must be a positive number");
10959 }
10960 }
10961
10962 public int getDx() {
10963 return mDx;
10964 }
10965
10966 public void setDx(int dx) {
10967 mChanged = true;
10968 mDx = dx;
10969 }
10970
10971 public int getDy() {
10972 return mDy;
10973 }
10974
10975 public void setDy(int dy) {
10976 mChanged = true;
10977 mDy = dy;
10978 }
10979
10980 public int getDuration() {
10981 return mDuration;
10982 }
10983
10984 public void setDuration(int duration) {
10985 mChanged = true;
10986 mDuration = duration;
10987 }
10988
10989 public Interpolator getInterpolator() {
10990 return mInterpolator;
10991 }
10992
10993 /**
10994 * Sets the interpolator to calculate scroll steps
10995 * @param interpolator The interpolator to use. If you specify an interpolator, you must
10996 * also set the duration.
10997 * @see #setDuration(int)
10998 */
10999 public void setInterpolator(Interpolator interpolator) {
11000 mChanged = true;
11001 mInterpolator = interpolator;
11002 }
11003
11004 /**
11005 * Updates the action with given parameters.
11006 * @param dx Pixels to scroll horizontally
11007 * @param dy Pixels to scroll vertically
11008 * @param duration Duration of the animation in milliseconds
11009 * @param interpolator Interpolator to be used when calculating scroll position in each
11010 * animation step
11011 */
11012 public void update(int dx, int dy, int duration, Interpolator interpolator) {
11013 mDx = dx;
11014 mDy = dy;
11015 mDuration = duration;
11016 mInterpolator = interpolator;
11017 mChanged = true;
11018 }
11019 }
11020
11021 /**
11022 * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager}
11023 * to provide a hint to a {@link SmoothScroller} about the location of the target position.
11024 */
11025 public interface ScrollVectorProvider {
11026 /**
11027 * Should calculate the vector that points to the direction where the target position
11028 * can be found.
11029 * <p>
11030 * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards
11031 * the target position.
11032 * <p>
11033 * The magnitude of the vector is not important. It is always normalized before being
11034 * used by the {@link LinearSmoothScroller}.
11035 * <p>
11036 * LayoutManager should not check whether the position exists in the adapter or not.
11037 *
11038 * @param targetPosition the target position to which the returned vector should point
11039 *
11040 * @return the scroll vector for a given position.
11041 */
11042 PointF computeScrollVectorForPosition(int targetPosition);
11043 }
11044 }
11045
11046 static class AdapterDataObservable extends Observable<AdapterDataObserver> {
11047 public boolean hasObservers() {
11048 return !mObservers.isEmpty();
11049 }
11050
11051 public void notifyChanged() {
11052 // since onChanged() is implemented by the app, it could do anything, including
11053 // removing itself from {@link mObservers} - and that could cause problems if
11054 // an iterator is used on the ArrayList {@link mObservers}.
11055 // to avoid such problems, just march thru the list in the reverse order.
11056 for (int i = mObservers.size() - 1; i >= 0; i--) {
11057 mObservers.get(i).onChanged();
11058 }
11059 }
11060
11061 public void notifyItemRangeChanged(int positionStart, int itemCount) {
11062 notifyItemRangeChanged(positionStart, itemCount, null);
11063 }
11064
11065 public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
11066 // since onItemRangeChanged() is implemented by the app, it could do anything, including
11067 // removing itself from {@link mObservers} - and that could cause problems if
11068 // an iterator is used on the ArrayList {@link mObservers}.
11069 // to avoid such problems, just march thru the list in the reverse order.
11070 for (int i = mObservers.size() - 1; i >= 0; i--) {
11071 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
11072 }
11073 }
11074
11075 public void notifyItemRangeInserted(int positionStart, int itemCount) {
11076 // since onItemRangeInserted() is implemented by the app, it could do anything,
11077 // including removing itself from {@link mObservers} - and that could cause problems if
11078 // an iterator is used on the ArrayList {@link mObservers}.
11079 // to avoid such problems, just march thru the list in the reverse order.
11080 for (int i = mObservers.size() - 1; i >= 0; i--) {
11081 mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
11082 }
11083 }
11084
11085 public void notifyItemRangeRemoved(int positionStart, int itemCount) {
11086 // since onItemRangeRemoved() is implemented by the app, it could do anything, including
11087 // removing itself from {@link mObservers} - and that could cause problems if
11088 // an iterator is used on the ArrayList {@link mObservers}.
11089 // to avoid such problems, just march thru the list in the reverse order.
11090 for (int i = mObservers.size() - 1; i >= 0; i--) {
11091 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
11092 }
11093 }
11094
11095 public void notifyItemMoved(int fromPosition, int toPosition) {
11096 for (int i = mObservers.size() - 1; i >= 0; i--) {
11097 mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
11098 }
11099 }
11100 }
11101
11102 /**
11103 * This is public so that the CREATOR can be access on cold launch.
11104 * @hide
11105 */
11106 public static class SavedState extends AbsSavedState {
11107
11108 Parcelable mLayoutState;
11109
11110 /**
11111 * called by CREATOR
11112 */
11113 SavedState(Parcel in) {
11114 super(in);
11115 mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
11116 }
11117
11118 /**
11119 * Called by onSaveInstanceState
11120 */
11121 SavedState(Parcelable superState) {
11122 super(superState);
11123 }
11124
11125 @Override
11126 public void writeToParcel(Parcel dest, int flags) {
11127 super.writeToParcel(dest, flags);
11128 dest.writeParcelable(mLayoutState, 0);
11129 }
11130
11131 void copyFrom(SavedState other) {
11132 mLayoutState = other.mLayoutState;
11133 }
11134
11135 public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
11136 @Override
11137 public SavedState createFromParcel(Parcel in) {
11138 return new SavedState(in);
11139 }
11140
11141 @Override
11142 public SavedState[] newArray(int size) {
11143 return new SavedState[size];
11144 }
11145 };
11146 }
11147 /**
11148 * <p>Contains useful information about the current RecyclerView state like target scroll
11149 * position or view focus. State object can also keep arbitrary data, identified by resource
11150 * ids.</p>
11151 * <p>Often times, RecyclerView components will need to pass information between each other.
11152 * To provide a well defined data bus between components, RecyclerView passes the same State
11153 * object to component callbacks and these components can use it to exchange data.</p>
11154 * <p>If you implement custom components, you can use State's put/get/remove methods to pass
11155 * data between your components without needing to manage their lifecycles.</p>
11156 */
11157 public static class State {
11158 static final int STEP_START = 1;
11159 static final int STEP_LAYOUT = 1 << 1;
11160 static final int STEP_ANIMATIONS = 1 << 2;
11161
11162 void assertLayoutStep(int accepted) {
11163 if ((accepted & mLayoutStep) == 0) {
11164 throw new IllegalStateException("Layout state should be one of "
11165 + Integer.toBinaryString(accepted) + " but it is "
11166 + Integer.toBinaryString(mLayoutStep));
11167 }
11168 }
11169
11170
11171 /** Owned by SmoothScroller */
11172 private int mTargetPosition = RecyclerView.NO_POSITION;
11173
11174 private SparseArray<Object> mData;
11175
11176 ////////////////////////////////////////////////////////////////////////////////////////////
11177 // Fields below are carried from one layout pass to the next
11178 ////////////////////////////////////////////////////////////////////////////////////////////
11179
11180 /**
11181 * Number of items adapter had in the previous layout.
11182 */
11183 int mPreviousLayoutItemCount = 0;
11184
11185 /**
11186 * Number of items that were NOT laid out but has been deleted from the adapter after the
11187 * previous layout.
11188 */
11189 int mDeletedInvisibleItemCountSincePreviousLayout = 0;
11190
11191 ////////////////////////////////////////////////////////////////////////////////////////////
11192 // Fields below must be updated or cleared before they are used (generally before a pass)
11193 ////////////////////////////////////////////////////////////////////////////////////////////
11194
11195 @IntDef(flag = true, value = {
11196 STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
11197 })
11198 @Retention(RetentionPolicy.SOURCE)
11199 @interface LayoutState {}
11200
11201 @LayoutState
11202 int mLayoutStep = STEP_START;
11203
11204 /**
11205 * Number of items adapter has.
11206 */
11207 int mItemCount = 0;
11208
11209 boolean mStructureChanged = false;
11210
11211 boolean mInPreLayout = false;
11212
11213 boolean mTrackOldChangeHolders = false;
11214
11215 boolean mIsMeasuring = false;
11216
11217 ////////////////////////////////////////////////////////////////////////////////////////////
11218 // Fields below are always reset outside of the pass (or passes) that use them
11219 ////////////////////////////////////////////////////////////////////////////////////////////
11220
11221 boolean mRunSimpleAnimations = false;
11222
11223 boolean mRunPredictiveAnimations = false;
11224
11225 /**
11226 * This data is saved before a layout calculation happens. After the layout is finished,
11227 * if the previously focused view has been replaced with another view for the same item, we
11228 * move the focus to the new item automatically.
11229 */
11230 int mFocusedItemPosition;
11231 long mFocusedItemId;
11232 // when a sub child has focus, record its id and see if we can directly request focus on
11233 // that one instead
11234 int mFocusedSubChildId;
11235
11236 ////////////////////////////////////////////////////////////////////////////////////////////
11237
11238 State reset() {
11239 mTargetPosition = RecyclerView.NO_POSITION;
11240 if (mData != null) {
11241 mData.clear();
11242 }
11243 mItemCount = 0;
11244 mStructureChanged = false;
11245 mIsMeasuring = false;
11246 return this;
11247 }
11248
11249 /**
11250 * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially
11251 * prior to any layout passes.
11252 *
11253 * <p>Don't touch any state stored between layout passes, only reset per-layout state, so
11254 * that Recycler#getViewForPosition() can function safely.</p>
11255 */
11256 void prepareForNestedPrefetch(Adapter adapter) {
11257 mLayoutStep = STEP_START;
11258 mItemCount = adapter.getItemCount();
11259 mStructureChanged = false;
11260 mInPreLayout = false;
11261 mTrackOldChangeHolders = false;
11262 mIsMeasuring = false;
11263 }
11264
11265 /**
11266 * Returns true if the RecyclerView is currently measuring the layout. This value is
11267 * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView
11268 * has non-exact measurement specs.
11269 * <p>
11270 * Note that if the LayoutManager supports predictive animations and it is calculating the
11271 * pre-layout step, this value will be {@code false} even if the RecyclerView is in
11272 * {@code onMeasure} call. This is because pre-layout means the previous state of the
11273 * RecyclerView and measurements made for that state cannot change the RecyclerView's size.
11274 * LayoutManager is always guaranteed to receive another call to
11275 * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens.
11276 *
11277 * @return True if the RecyclerView is currently calculating its bounds, false otherwise.
11278 */
11279 public boolean isMeasuring() {
11280 return mIsMeasuring;
11281 }
11282
11283 /**
11284 * Returns true if
11285 * @return
11286 */
11287 public boolean isPreLayout() {
11288 return mInPreLayout;
11289 }
11290
11291 /**
11292 * Returns whether RecyclerView will run predictive animations in this layout pass
11293 * or not.
11294 *
11295 * @return true if RecyclerView is calculating predictive animations to be run at the end
11296 * of the layout pass.
11297 */
11298 public boolean willRunPredictiveAnimations() {
11299 return mRunPredictiveAnimations;
11300 }
11301
11302 /**
11303 * Returns whether RecyclerView will run simple animations in this layout pass
11304 * or not.
11305 *
11306 * @return true if RecyclerView is calculating simple animations to be run at the end of
11307 * the layout pass.
11308 */
11309 public boolean willRunSimpleAnimations() {
11310 return mRunSimpleAnimations;
11311 }
11312
11313 /**
11314 * Removes the mapping from the specified id, if there was any.
11315 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to
11316 * preserve cross functionality and avoid conflicts.
11317 */
11318 public void remove(int resourceId) {
11319 if (mData == null) {
11320 return;
11321 }
11322 mData.remove(resourceId);
11323 }
11324
11325 /**
11326 * Gets the Object mapped from the specified id, or <code>null</code>
11327 * if no such data exists.
11328 *
11329 * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.*
11330 * to
11331 * preserve cross functionality and avoid conflicts.
11332 */
11333 public <T> T get(int resourceId) {
11334 if (mData == null) {
11335 return null;
11336 }
11337 return (T) mData.get(resourceId);
11338 }
11339
11340 /**
11341 * Adds a mapping from the specified id to the specified value, replacing the previous
11342 * mapping from the specified key if there was one.
11343 *
11344 * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to
11345 * preserve cross functionality and avoid conflicts.
11346 * @param data The data you want to associate with the resourceId.
11347 */
11348 public void put(int resourceId, Object data) {
11349 if (mData == null) {
11350 mData = new SparseArray<Object>();
11351 }
11352 mData.put(resourceId, data);
11353 }
11354
11355 /**
11356 * If scroll is triggered to make a certain item visible, this value will return the
11357 * adapter index of that item.
11358 * @return Adapter index of the target item or
11359 * {@link RecyclerView#NO_POSITION} if there is no target
11360 * position.
11361 */
11362 public int getTargetScrollPosition() {
11363 return mTargetPosition;
11364 }
11365
11366 /**
11367 * Returns if current scroll has a target position.
11368 * @return true if scroll is being triggered to make a certain position visible
11369 * @see #getTargetScrollPosition()
11370 */
11371 public boolean hasTargetScrollPosition() {
11372 return mTargetPosition != RecyclerView.NO_POSITION;
11373 }
11374
11375 /**
11376 * @return true if the structure of the data set has changed since the last call to
11377 * onLayoutChildren, false otherwise
11378 */
11379 public boolean didStructureChange() {
11380 return mStructureChanged;
11381 }
11382
11383 /**
11384 * Returns the total number of items that can be laid out. Note that this number is not
11385 * necessarily equal to the number of items in the adapter, so you should always use this
11386 * number for your position calculations and never access the adapter directly.
11387 * <p>
11388 * RecyclerView listens for Adapter's notify events and calculates the effects of adapter
11389 * data changes on existing Views. These calculations are used to decide which animations
11390 * should be run.
11391 * <p>
11392 * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to
11393 * present the correct state to LayoutManager in pre-layout pass.
11394 * <p>
11395 * For example, a newly added item is not included in pre-layout item count because
11396 * pre-layout reflects the contents of the adapter before the item is added. Behind the
11397 * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that
11398 * LayoutManager does not know about the new item's existence in pre-layout. The item will
11399 * be available in second layout pass and will be included in the item count. Similar
11400 * adjustments are made for moved and removed items as well.
11401 * <p>
11402 * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method.
11403 *
11404 * @return The number of items currently available
11405 * @see LayoutManager#getItemCount()
11406 */
11407 public int getItemCount() {
11408 return mInPreLayout
11409 ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout)
11410 : mItemCount;
11411 }
11412
11413 @Override
11414 public String toString() {
11415 return "State{"
11416 + "mTargetPosition=" + mTargetPosition
11417 + ", mData=" + mData
11418 + ", mItemCount=" + mItemCount
11419 + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount
11420 + ", mDeletedInvisibleItemCountSincePreviousLayout="
11421 + mDeletedInvisibleItemCountSincePreviousLayout
11422 + ", mStructureChanged=" + mStructureChanged
11423 + ", mInPreLayout=" + mInPreLayout
11424 + ", mRunSimpleAnimations=" + mRunSimpleAnimations
11425 + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations
11426 + '}';
11427 }
11428 }
11429
11430 /**
11431 * This class defines the behavior of fling if the developer wishes to handle it.
11432 * <p>
11433 * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior.
11434 *
11435 * @see #setOnFlingListener(OnFlingListener)
11436 */
11437 public abstract static class OnFlingListener {
11438
11439 /**
11440 * Override this to handle a fling given the velocities in both x and y directions.
11441 * Note that this method will only be called if the associated {@link LayoutManager}
11442 * supports scrolling and the fling is not handled by nested scrolls first.
11443 *
11444 * @param velocityX the fling velocity on the X axis
11445 * @param velocityY the fling velocity on the Y axis
11446 *
11447 * @return true if the fling washandled, false otherwise.
11448 */
11449 public abstract boolean onFling(int velocityX, int velocityY);
11450 }
11451
11452 /**
11453 * Internal listener that manages items after animations finish. This is how items are
11454 * retained (not recycled) during animations, but allowed to be recycled afterwards.
11455 * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished()
11456 * method on the animator's listener when it is done animating any item.
11457 */
11458 private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
11459
11460 ItemAnimatorRestoreListener() {
11461 }
11462
11463 @Override
11464 public void onAnimationFinished(ViewHolder item) {
11465 item.setIsRecyclable(true);
11466 if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
11467 item.mShadowedHolder = null;
11468 }
11469 // always null this because an OldViewHolder can never become NewViewHolder w/o being
11470 // recycled.
11471 item.mShadowingHolder = null;
11472 if (!item.shouldBeKeptAsChild()) {
11473 if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
11474 removeDetachedView(item.itemView, false);
11475 }
11476 }
11477 }
11478 }
11479
11480 /**
11481 * This class defines the animations that take place on items as changes are made
11482 * to the adapter.
11483 *
11484 * Subclasses of ItemAnimator can be used to implement custom animations for actions on
11485 * ViewHolder items. The RecyclerView will manage retaining these items while they
11486 * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)}
11487 * when a ViewHolder's animation is finished. In other words, there must be a matching
11488 * {@link #dispatchAnimationFinished(ViewHolder)} call for each
11489 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()},
11490 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11491 * animateChange()}
11492 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()},
11493 * and
11494 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11495 * animateDisappearance()} call.
11496 *
11497 * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p>
11498 *
11499 * @see #setItemAnimator(ItemAnimator)
11500 */
11501 @SuppressWarnings("UnusedParameters")
11502 public abstract static class ItemAnimator {
11503
11504 /**
11505 * The Item represented by this ViewHolder is updated.
11506 * <p>
11507 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11508 */
11509 public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE;
11510
11511 /**
11512 * The Item represented by this ViewHolder is removed from the adapter.
11513 * <p>
11514 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11515 */
11516 public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED;
11517
11518 /**
11519 * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content
11520 * represented by this ViewHolder is invalid.
11521 * <p>
11522 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11523 */
11524 public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID;
11525
11526 /**
11527 * The position of the Item represented by this ViewHolder has been changed. This flag is
11528 * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to
11529 * any adapter change that may have a side effect on this item. (e.g. The item before this
11530 * one has been removed from the Adapter).
11531 * <p>
11532 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11533 */
11534 public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED;
11535
11536 /**
11537 * This ViewHolder was not laid out but has been added to the layout in pre-layout state
11538 * by the {@link LayoutManager}. This means that the item was already in the Adapter but
11539 * invisible and it may become visible in the post layout phase. LayoutManagers may prefer
11540 * to add new items in pre-layout to specify their virtual location when they are invisible
11541 * (e.g. to specify the item should <i>animate in</i> from below the visible area).
11542 * <p>
11543 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11544 */
11545 public static final int FLAG_APPEARED_IN_PRE_LAYOUT =
11546 ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT;
11547
11548 /**
11549 * The set of flags that might be passed to
11550 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11551 */
11552 @IntDef(flag = true, value = {
11553 FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED,
11554 FLAG_APPEARED_IN_PRE_LAYOUT
11555 })
11556 @Retention(RetentionPolicy.SOURCE)
11557 public @interface AdapterChanges {}
11558 private ItemAnimatorListener mListener = null;
11559 private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners =
11560 new ArrayList<ItemAnimatorFinishedListener>();
11561
11562 private long mAddDuration = 120;
11563 private long mRemoveDuration = 120;
11564 private long mMoveDuration = 250;
11565 private long mChangeDuration = 250;
11566
11567 /**
11568 * Gets the current duration for which all move animations will run.
11569 *
11570 * @return The current move duration
11571 */
11572 public long getMoveDuration() {
11573 return mMoveDuration;
11574 }
11575
11576 /**
11577 * Sets the duration for which all move animations will run.
11578 *
11579 * @param moveDuration The move duration
11580 */
11581 public void setMoveDuration(long moveDuration) {
11582 mMoveDuration = moveDuration;
11583 }
11584
11585 /**
11586 * Gets the current duration for which all add animations will run.
11587 *
11588 * @return The current add duration
11589 */
11590 public long getAddDuration() {
11591 return mAddDuration;
11592 }
11593
11594 /**
11595 * Sets the duration for which all add animations will run.
11596 *
11597 * @param addDuration The add duration
11598 */
11599 public void setAddDuration(long addDuration) {
11600 mAddDuration = addDuration;
11601 }
11602
11603 /**
11604 * Gets the current duration for which all remove animations will run.
11605 *
11606 * @return The current remove duration
11607 */
11608 public long getRemoveDuration() {
11609 return mRemoveDuration;
11610 }
11611
11612 /**
11613 * Sets the duration for which all remove animations will run.
11614 *
11615 * @param removeDuration The remove duration
11616 */
11617 public void setRemoveDuration(long removeDuration) {
11618 mRemoveDuration = removeDuration;
11619 }
11620
11621 /**
11622 * Gets the current duration for which all change animations will run.
11623 *
11624 * @return The current change duration
11625 */
11626 public long getChangeDuration() {
11627 return mChangeDuration;
11628 }
11629
11630 /**
11631 * Sets the duration for which all change animations will run.
11632 *
11633 * @param changeDuration The change duration
11634 */
11635 public void setChangeDuration(long changeDuration) {
11636 mChangeDuration = changeDuration;
11637 }
11638
11639 /**
11640 * Internal only:
11641 * Sets the listener that must be called when the animator is finished
11642 * animating the item (or immediately if no animation happens). This is set
11643 * internally and is not intended to be set by external code.
11644 *
11645 * @param listener The listener that must be called.
11646 */
11647 void setListener(ItemAnimatorListener listener) {
11648 mListener = listener;
11649 }
11650
11651 /**
11652 * Called by the RecyclerView before the layout begins. Item animator should record
11653 * necessary information about the View before it is potentially rebound, moved or removed.
11654 * <p>
11655 * The data returned from this method will be passed to the related <code>animate**</code>
11656 * methods.
11657 * <p>
11658 * Note that this method may be called after pre-layout phase if LayoutManager adds new
11659 * Views to the layout in pre-layout pass.
11660 * <p>
11661 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
11662 * the View and the adapter change flags.
11663 *
11664 * @param state The current State of RecyclerView which includes some useful data
11665 * about the layout that will be calculated.
11666 * @param viewHolder The ViewHolder whose information should be recorded.
11667 * @param changeFlags Additional information about what changes happened in the Adapter
11668 * about the Item represented by this ViewHolder. For instance, if
11669 * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set.
11670 * @param payloads The payload list that was previously passed to
11671 * {@link Adapter#notifyItemChanged(int, Object)} or
11672 * {@link Adapter#notifyItemRangeChanged(int, int, Object)}.
11673 *
11674 * @return An ItemHolderInfo instance that preserves necessary information about the
11675 * ViewHolder. This object will be passed back to related <code>animate**</code> methods
11676 * after layout is complete.
11677 *
11678 * @see #recordPostLayoutInformation(State, ViewHolder)
11679 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11680 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11681 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11682 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11683 */
11684 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
11685 @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
11686 @NonNull List<Object> payloads) {
11687 return obtainHolderInfo().setFrom(viewHolder);
11688 }
11689
11690 /**
11691 * Called by the RecyclerView after the layout is complete. Item animator should record
11692 * necessary information about the View's final state.
11693 * <p>
11694 * The data returned from this method will be passed to the related <code>animate**</code>
11695 * methods.
11696 * <p>
11697 * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
11698 * the View.
11699 *
11700 * @param state The current State of RecyclerView which includes some useful data about
11701 * the layout that will be calculated.
11702 * @param viewHolder The ViewHolder whose information should be recorded.
11703 *
11704 * @return An ItemHolderInfo that preserves necessary information about the ViewHolder.
11705 * This object will be passed back to related <code>animate**</code> methods when
11706 * RecyclerView decides how items should be animated.
11707 *
11708 * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
11709 * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11710 * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11711 * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11712 * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11713 */
11714 public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
11715 @NonNull ViewHolder viewHolder) {
11716 return obtainHolderInfo().setFrom(viewHolder);
11717 }
11718
11719 /**
11720 * Called by the RecyclerView when a ViewHolder has disappeared from the layout.
11721 * <p>
11722 * This means that the View was a child of the LayoutManager when layout started but has
11723 * been removed by the LayoutManager. It might have been removed from the adapter or simply
11724 * become invisible due to other factors. You can distinguish these two cases by checking
11725 * the change flags that were passed to
11726 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11727 * <p>
11728 * Note that when a ViewHolder both changes and disappears in the same layout pass, the
11729 * animation callback method which will be called by the RecyclerView depends on the
11730 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
11731 * LayoutManager's decision whether to layout the changed version of a disappearing
11732 * ViewHolder or not. RecyclerView will call
11733 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11734 * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator
11735 * returns {@code false} from
11736 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
11737 * LayoutManager lays out a new disappearing view that holds the updated information.
11738 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
11739 * <p>
11740 * If LayoutManager supports predictive animations, it might provide a target disappear
11741 * location for the View by laying it out in that location. When that happens,
11742 * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the
11743 * response of that call will be passed to this method as the <code>postLayoutInfo</code>.
11744 * <p>
11745 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
11746 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
11747 * decides not to animate the view).
11748 *
11749 * @param viewHolder The ViewHolder which should be animated
11750 * @param preLayoutInfo The information that was returned from
11751 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11752 * @param postLayoutInfo The information that was returned from
11753 * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be
11754 * null if the LayoutManager did not layout the item.
11755 *
11756 * @return true if a later call to {@link #runPendingAnimations()} is requested,
11757 * false otherwise.
11758 */
11759 public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
11760 @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
11761
11762 /**
11763 * Called by the RecyclerView when a ViewHolder is added to the layout.
11764 * <p>
11765 * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started
11766 * but has been added by the LayoutManager. It might be newly added to the adapter or
11767 * simply become visible due to other factors.
11768 * <p>
11769 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
11770 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
11771 * decides not to animate the view).
11772 *
11773 * @param viewHolder The ViewHolder which should be animated
11774 * @param preLayoutInfo The information that was returned from
11775 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11776 * Might be null if Item was just added to the adapter or
11777 * LayoutManager does not support predictive animations or it could
11778 * not predict that this ViewHolder will become visible.
11779 * @param postLayoutInfo The information that was returned from {@link
11780 * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11781 *
11782 * @return true if a later call to {@link #runPendingAnimations()} is requested,
11783 * false otherwise.
11784 */
11785 public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
11786 @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
11787
11788 /**
11789 * Called by the RecyclerView when a ViewHolder is present in both before and after the
11790 * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call
11791 * for it or a {@link Adapter#notifyDataSetChanged()} call.
11792 * <p>
11793 * This ViewHolder still represents the same data that it was representing when the layout
11794 * started but its position / size may be changed by the LayoutManager.
11795 * <p>
11796 * If the Item's layout position didn't change, RecyclerView still calls this method because
11797 * it does not track this information (or does not necessarily know that an animation is
11798 * not required). Your ItemAnimator should handle this case and if there is nothing to
11799 * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return
11800 * <code>false</code>.
11801 * <p>
11802 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
11803 * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
11804 * decides not to animate the view).
11805 *
11806 * @param viewHolder The ViewHolder which should be animated
11807 * @param preLayoutInfo The information that was returned from
11808 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11809 * @param postLayoutInfo The information that was returned from {@link
11810 * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11811 *
11812 * @return true if a later call to {@link #runPendingAnimations()} is requested,
11813 * false otherwise.
11814 */
11815 public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder,
11816 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
11817
11818 /**
11819 * Called by the RecyclerView when an adapter item is present both before and after the
11820 * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call
11821 * for it. This method may also be called when
11822 * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that
11823 * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when
11824 * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called,
11825 * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be
11826 * called for the new ViewHolder and the old one will be recycled.
11827 * <p>
11828 * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is
11829 * a good possibility that item contents didn't really change but it is rebound from the
11830 * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the
11831 * screen didn't change and your animator should handle this case as well and avoid creating
11832 * unnecessary animations.
11833 * <p>
11834 * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the
11835 * previous presentation of the item as-is and supply a new ViewHolder for the updated
11836 * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}.
11837 * This is useful if you don't know the contents of the Item and would like
11838 * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique).
11839 * <p>
11840 * When you are writing a custom item animator for your layout, it might be more performant
11841 * and elegant to re-use the same ViewHolder and animate the content changes manually.
11842 * <p>
11843 * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change.
11844 * If the Item's view type has changed or ItemAnimator returned <code>false</code> for
11845 * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the
11846 * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances
11847 * which represent the same Item. In that case, only the new ViewHolder is visible
11848 * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations.
11849 * <p>
11850 * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct
11851 * ViewHolder when their animation is complete
11852 * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to
11853 * animate the view).
11854 * <p>
11855 * If oldHolder and newHolder are the same instance, you should call
11856 * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>.
11857 * <p>
11858 * Note that when a ViewHolder both changes and disappears in the same layout pass, the
11859 * animation callback method which will be called by the RecyclerView depends on the
11860 * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
11861 * LayoutManager's decision whether to layout the changed version of a disappearing
11862 * ViewHolder or not. RecyclerView will call
11863 * {@code animateChange} instead of
11864 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11865 * animateDisappearance} if and only if the ItemAnimator returns {@code false} from
11866 * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
11867 * LayoutManager lays out a new disappearing view that holds the updated information.
11868 * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
11869 *
11870 * @param oldHolder The ViewHolder before the layout is started, might be the same
11871 * instance with newHolder.
11872 * @param newHolder The ViewHolder after the layout is finished, might be the same
11873 * instance with oldHolder.
11874 * @param preLayoutInfo The information that was returned from
11875 * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11876 * @param postLayoutInfo The information that was returned from {@link
11877 * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
11878 *
11879 * @return true if a later call to {@link #runPendingAnimations()} is requested,
11880 * false otherwise.
11881 */
11882 public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
11883 @NonNull ViewHolder newHolder,
11884 @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
11885
11886 @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
11887 int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
11888 if (viewHolder.isInvalid()) {
11889 return FLAG_INVALIDATED;
11890 }
11891 if ((flags & FLAG_INVALIDATED) == 0) {
11892 final int oldPos = viewHolder.getOldPosition();
11893 final int pos = viewHolder.getAdapterPosition();
11894 if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) {
11895 flags |= FLAG_MOVED;
11896 }
11897 }
11898 return flags;
11899 }
11900
11901 /**
11902 * Called when there are pending animations waiting to be started. This state
11903 * is governed by the return values from
11904 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11905 * animateAppearance()},
11906 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11907 * animateChange()}
11908 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11909 * animatePersistence()}, and
11910 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11911 * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be
11912 * called later to start the associated animations. runPendingAnimations() will be scheduled
11913 * to be run on the next frame.
11914 */
11915 public abstract void runPendingAnimations();
11916
11917 /**
11918 * Method called when an animation on a view should be ended immediately.
11919 * This could happen when other events, like scrolling, occur, so that
11920 * animating views can be quickly put into their proper end locations.
11921 * Implementations should ensure that any animations running on the item
11922 * are canceled and affected properties are set to their end values.
11923 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
11924 * animation since the animations are effectively done when this method is called.
11925 *
11926 * @param item The item for which an animation should be stopped.
11927 */
11928 public abstract void endAnimation(ViewHolder item);
11929
11930 /**
11931 * Method called when all item animations should be ended immediately.
11932 * This could happen when other events, like scrolling, occur, so that
11933 * animating views can be quickly put into their proper end locations.
11934 * Implementations should ensure that any animations running on any items
11935 * are canceled and affected properties are set to their end values.
11936 * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
11937 * animation since the animations are effectively done when this method is called.
11938 */
11939 public abstract void endAnimations();
11940
11941 /**
11942 * Method which returns whether there are any item animations currently running.
11943 * This method can be used to determine whether to delay other actions until
11944 * animations end.
11945 *
11946 * @return true if there are any item animations currently running, false otherwise.
11947 */
11948 public abstract boolean isRunning();
11949
11950 /**
11951 * Method to be called by subclasses when an animation is finished.
11952 * <p>
11953 * For each call RecyclerView makes to
11954 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11955 * animateAppearance()},
11956 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11957 * animatePersistence()}, or
11958 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11959 * animateDisappearance()}, there
11960 * should
11961 * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass.
11962 * <p>
11963 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
11964 * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
11965 * and <code>newHolder</code> (if they are not the same instance).
11966 *
11967 * @param viewHolder The ViewHolder whose animation is finished.
11968 * @see #onAnimationFinished(ViewHolder)
11969 */
11970 public final void dispatchAnimationFinished(ViewHolder viewHolder) {
11971 onAnimationFinished(viewHolder);
11972 if (mListener != null) {
11973 mListener.onAnimationFinished(viewHolder);
11974 }
11975 }
11976
11977 /**
11978 * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the
11979 * ItemAnimator.
11980 *
11981 * @param viewHolder The ViewHolder whose animation is finished. There might still be other
11982 * animations running on this ViewHolder.
11983 * @see #dispatchAnimationFinished(ViewHolder)
11984 */
11985 public void onAnimationFinished(ViewHolder viewHolder) {
11986 }
11987
11988 /**
11989 * Method to be called by subclasses when an animation is started.
11990 * <p>
11991 * For each call RecyclerView makes to
11992 * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11993 * animateAppearance()},
11994 * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11995 * animatePersistence()}, or
11996 * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
11997 * animateDisappearance()}, there should be a matching
11998 * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass.
11999 * <p>
12000 * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
12001 * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
12002 * and <code>newHolder</code> (if they are not the same instance).
12003 * <p>
12004 * If your ItemAnimator decides not to animate a ViewHolder, it should call
12005 * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling
12006 * {@link #dispatchAnimationStarted(ViewHolder)}.
12007 *
12008 * @param viewHolder The ViewHolder whose animation is starting.
12009 * @see #onAnimationStarted(ViewHolder)
12010 */
12011 public final void dispatchAnimationStarted(ViewHolder viewHolder) {
12012 onAnimationStarted(viewHolder);
12013 }
12014
12015 /**
12016 * Called when a new animation is started on the given ViewHolder.
12017 *
12018 * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder
12019 * might already be animating and this might be another animation.
12020 * @see #dispatchAnimationStarted(ViewHolder)
12021 */
12022 public void onAnimationStarted(ViewHolder viewHolder) {
12023
12024 }
12025
12026 /**
12027 * Like {@link #isRunning()}, this method returns whether there are any item
12028 * animations currently running. Additionally, the listener passed in will be called
12029 * when there are no item animations running, either immediately (before the method
12030 * returns) if no animations are currently running, or when the currently running
12031 * animations are {@link #dispatchAnimationsFinished() finished}.
12032 *
12033 * <p>Note that the listener is transient - it is either called immediately and not
12034 * stored at all, or stored only until it is called when running animations
12035 * are finished sometime later.</p>
12036 *
12037 * @param listener A listener to be called immediately if no animations are running
12038 * or later when currently-running animations have finished. A null listener is
12039 * equivalent to calling {@link #isRunning()}.
12040 * @return true if there are any item animations currently running, false otherwise.
12041 */
12042 public final boolean isRunning(ItemAnimatorFinishedListener listener) {
12043 boolean running = isRunning();
12044 if (listener != null) {
12045 if (!running) {
12046 listener.onAnimationsFinished();
12047 } else {
12048 mFinishedListeners.add(listener);
12049 }
12050 }
12051 return running;
12052 }
12053
12054 /**
12055 * When an item is changed, ItemAnimator can decide whether it wants to re-use
12056 * the same ViewHolder for animations or RecyclerView should create a copy of the
12057 * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
12058 * <p>
12059 * Note that this method will only be called if the {@link ViewHolder} still has the same
12060 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
12061 * both {@link ViewHolder}s in the
12062 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
12063 * <p>
12064 * If your application is using change payloads, you can override
12065 * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads.
12066 *
12067 * @param viewHolder The ViewHolder which represents the changed item's old content.
12068 *
12069 * @return True if RecyclerView should just rebind to the same ViewHolder or false if
12070 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
12071 * ItemAnimator to animate. Default implementation returns <code>true</code>.
12072 *
12073 * @see #canReuseUpdatedViewHolder(ViewHolder, List)
12074 */
12075 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {
12076 return true;
12077 }
12078
12079 /**
12080 * When an item is changed, ItemAnimator can decide whether it wants to re-use
12081 * the same ViewHolder for animations or RecyclerView should create a copy of the
12082 * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
12083 * <p>
12084 * Note that this method will only be called if the {@link ViewHolder} still has the same
12085 * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
12086 * both {@link ViewHolder}s in the
12087 * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
12088 *
12089 * @param viewHolder The ViewHolder which represents the changed item's old content.
12090 * @param payloads A non-null list of merged payloads that were sent with change
12091 * notifications. Can be empty if the adapter is invalidated via
12092 * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
12093 * payloads will be passed into
12094 * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
12095 * method <b>if</b> this method returns <code>true</code>.
12096 *
12097 * @return True if RecyclerView should just rebind to the same ViewHolder or false if
12098 * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
12099 * ItemAnimator to animate. Default implementation calls
12100 * {@link #canReuseUpdatedViewHolder(ViewHolder)}.
12101 *
12102 * @see #canReuseUpdatedViewHolder(ViewHolder)
12103 */
12104 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
12105 @NonNull List<Object> payloads) {
12106 return canReuseUpdatedViewHolder(viewHolder);
12107 }
12108
12109 /**
12110 * This method should be called by ItemAnimator implementations to notify
12111 * any listeners that all pending and active item animations are finished.
12112 */
12113 public final void dispatchAnimationsFinished() {
12114 final int count = mFinishedListeners.size();
12115 for (int i = 0; i < count; ++i) {
12116 mFinishedListeners.get(i).onAnimationsFinished();
12117 }
12118 mFinishedListeners.clear();
12119 }
12120
12121 /**
12122 * Returns a new {@link ItemHolderInfo} which will be used to store information about the
12123 * ViewHolder. This information will later be passed into <code>animate**</code> methods.
12124 * <p>
12125 * You can override this method if you want to extend {@link ItemHolderInfo} and provide
12126 * your own instances.
12127 *
12128 * @return A new {@link ItemHolderInfo}.
12129 */
12130 public ItemHolderInfo obtainHolderInfo() {
12131 return new ItemHolderInfo();
12132 }
12133
12134 /**
12135 * The interface to be implemented by listeners to animation events from this
12136 * ItemAnimator. This is used internally and is not intended for developers to
12137 * create directly.
12138 */
12139 interface ItemAnimatorListener {
12140 void onAnimationFinished(ViewHolder item);
12141 }
12142
12143 /**
12144 * This interface is used to inform listeners when all pending or running animations
12145 * in an ItemAnimator are finished. This can be used, for example, to delay an action
12146 * in a data set until currently-running animations are complete.
12147 *
12148 * @see #isRunning(ItemAnimatorFinishedListener)
12149 */
12150 public interface ItemAnimatorFinishedListener {
12151 /**
12152 * Notifies when all pending or running animations in an ItemAnimator are finished.
12153 */
12154 void onAnimationsFinished();
12155 }
12156
12157 /**
12158 * A simple data structure that holds information about an item's bounds.
12159 * This information is used in calculating item animations. Default implementation of
12160 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and
12161 * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data
12162 * structure. You can extend this class if you would like to keep more information about
12163 * the Views.
12164 * <p>
12165 * If you want to provide your own implementation but still use `super` methods to record
12166 * basic information, you can override {@link #obtainHolderInfo()} to provide your own
12167 * instances.
12168 */
12169 public static class ItemHolderInfo {
12170
12171 /**
12172 * The left edge of the View (excluding decorations)
12173 */
12174 public int left;
12175
12176 /**
12177 * The top edge of the View (excluding decorations)
12178 */
12179 public int top;
12180
12181 /**
12182 * The right edge of the View (excluding decorations)
12183 */
12184 public int right;
12185
12186 /**
12187 * The bottom edge of the View (excluding decorations)
12188 */
12189 public int bottom;
12190
12191 /**
12192 * The change flags that were passed to
12193 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}.
12194 */
12195 @AdapterChanges
12196 public int changeFlags;
12197
12198 public ItemHolderInfo() {
12199 }
12200
12201 /**
12202 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
12203 * the given ViewHolder. Clears all {@link #changeFlags}.
12204 *
12205 * @param holder The ViewHolder whose bounds should be copied.
12206 * @return This {@link ItemHolderInfo}
12207 */
12208 public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) {
12209 return setFrom(holder, 0);
12210 }
12211
12212 /**
12213 * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
12214 * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter.
12215 *
12216 * @param holder The ViewHolder whose bounds should be copied.
12217 * @param flags The adapter change flags that were passed into
12218 * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int,
12219 * List)}.
12220 * @return This {@link ItemHolderInfo}
12221 */
12222 public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
12223 @AdapterChanges int flags) {
12224 final View view = holder.itemView;
12225 this.left = view.getLeft();
12226 this.top = view.getTop();
12227 this.right = view.getRight();
12228 this.bottom = view.getBottom();
12229 return this;
12230 }
12231 }
12232 }
12233
12234 @Override
12235 protected int getChildDrawingOrder(int childCount, int i) {
12236 if (mChildDrawingOrderCallback == null) {
12237 return super.getChildDrawingOrder(childCount, i);
12238 } else {
12239 return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i);
12240 }
12241 }
12242
12243 /**
12244 * A callback interface that can be used to alter the drawing order of RecyclerView children.
12245 * <p>
12246 * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case
12247 * that applies to that method also applies to this callback. For example, changing the drawing
12248 * order of two views will not have any effect if their elevation values are different since
12249 * elevation overrides the result of this callback.
12250 */
12251 public interface ChildDrawingOrderCallback {
12252 /**
12253 * Returns the index of the child to draw for this iteration. Override this
12254 * if you want to change the drawing order of children. By default, it
12255 * returns i.
12256 *
12257 * @param i The current iteration.
12258 * @return The index of the child to draw this iteration.
12259 *
12260 * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
12261 */
12262 int onGetChildDrawingOrder(int childCount, int i);
12263 }
12264}