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