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