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