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