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