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