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