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