RecyclerView.java revision bd254c5a170a60055b8c594441660e4fe819add7
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.Rect;
24import android.os.Build;
25import android.support.v4.util.Pools;
26import android.support.v4.view.MotionEventCompat;
27import android.support.v4.view.VelocityTrackerCompat;
28import android.support.v4.view.ViewCompat;
29import android.support.v4.widget.EdgeEffectCompat;
30import android.support.v4.widget.ScrollerCompat;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.util.SparseArray;
34import android.util.SparseIntArray;
35import android.view.FocusFinder;
36import android.view.MotionEvent;
37import android.view.VelocityTracker;
38import android.view.View;
39import android.view.ViewConfiguration;
40import android.view.ViewGroup;
41import android.view.ViewParent;
42import android.view.animation.Interpolator;
43
44import java.util.ArrayList;
45
46/**
47 * A flexible view for providing a limited window into a large data set.
48 *
49 * <h3>Glossary of terms:</h3>
50 *
51 * <ul>
52 *     <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
53 *     that represent items in a data set.</li>
54 *     <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
55 *     <li><em>Index:</em> The index of an attached child view as used in a call to
56 *     {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
57 *     <li><em>Binding:</em> The process of preparing a child view to display data corresponding
58 *     to a <em>position</em> within the adapter.</li>
59 *     <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
60 *     position may be placed in a cache for later reuse to display the same type of data again
61 *     later. This can drastically improve performance by skipping initial layout inflation
62 *     or construction.</li>
63 *     <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
64 *     state during layout. Scrap views may be reused without becoming fully detached
65 *     from the parent RecyclerView, either unmodified if no rebinding is required or modified
66 *     by the adapter if the view was considered <em>dirty</em>.</li>
67 *     <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
68 *     being displayed.</li>
69 * </ul>
70 */
71public class RecyclerView extends ViewGroup {
72    private static final String TAG = "RecyclerView";
73    private static final boolean DEBUG = false;
74    private static final boolean DISPATCH_TEMP_DETACH = false;
75
76    public static final int HORIZONTAL = 0;
77    public static final int VERTICAL = 1;
78
79    public static final int NO_POSITION = -1;
80    public static final long NO_ID = -1;
81    public static final int INVALID_TYPE = -1;
82
83    private static final int MAX_SCROLL_DURATION = 2000;
84
85    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
86
87    private final Recycler mRecycler = new Recycler();
88
89    /**
90     * Note: this Runnable is only ever posted if:
91     * 1) We've been through first layout
92     * 2) We know we have a fixed size (mHasFixedSize)
93     * 3) We're attached
94     */
95    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
96        public void run() {
97            eatRequestLayout();
98            updateChildViews();
99            resumeRequestLayout(true);
100        }
101    };
102
103    private final Rect mTempRect = new Rect();
104
105    private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
106    private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
107
108    private Adapter mAdapter;
109    private LayoutManager mLayout;
110    private RecyclerListener mRecyclerListener;
111    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
112    private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
113            new ArrayList<OnItemTouchListener>();
114    private OnItemTouchListener mActiveOnItemTouchListener;
115    private boolean mIsAttached;
116    private boolean mHasFixedSize;
117    private boolean mFirstLayoutComplete;
118    private boolean mEatRequestLayout;
119    private boolean mLayoutRequestEaten;
120    private boolean mAdapterUpdateDuringMeasure;
121    private boolean mStructureChanged;
122    private final boolean mPostUpdatesOnAnimation;
123
124    private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
125
126    private static final int INVALID_POINTER = -1;
127
128    /**
129     * The RecyclerView is not currently scrolling.
130     * @see #getScrollState()
131     */
132    public static final int SCROLL_STATE_IDLE = 0;
133
134    /**
135     * The RecyclerView is currently being dragged by outside input such as user touch input.
136     * @see #getScrollState()
137     */
138    public static final int SCROLL_STATE_DRAGGING = 1;
139
140    /**
141     * The RecyclerView is currently animating to a final position while not under
142     * outside control.
143     * @see #getScrollState()
144     */
145    public static final int SCROLL_STATE_SETTLING = 2;
146
147    // Touch/scrolling handling
148
149    private int mScrollState = SCROLL_STATE_IDLE;
150    private int mScrollPointerId = INVALID_POINTER;
151    private VelocityTracker mVelocityTracker;
152    private int mInitialTouchX;
153    private int mInitialTouchY;
154    private int mLastTouchX;
155    private int mLastTouchY;
156    private final int mTouchSlop;
157    private final int mMinFlingVelocity;
158    private final int mMaxFlingVelocity;
159
160    private final ViewFlinger mViewFlinger = new ViewFlinger();
161
162    private OnScrollListener mScrollListener;
163
164    private static final Interpolator sQuinticInterpolator = new Interpolator() {
165        public float getInterpolation(float t) {
166            t -= 1.0f;
167            return t * t * t * t * t + 1.0f;
168        }
169    };
170
171    public RecyclerView(Context context) {
172        this(context, null);
173    }
174
175    public RecyclerView(Context context, AttributeSet attrs) {
176        this(context, attrs, 0);
177    }
178
179    public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
180        super(context, attrs, defStyle);
181
182        final int version = Build.VERSION.SDK_INT;
183        mPostUpdatesOnAnimation = version >= 16;
184
185        final ViewConfiguration vc = ViewConfiguration.get(context);
186        mTouchSlop = vc.getScaledTouchSlop();
187        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
188        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
189
190        setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
191    }
192
193    /**
194     * RecyclerView can perform several optimizations if it can know in advance that changes in
195     * adapter content cannot change the size of the RecyclerView itself.
196     * If your use of RecyclerView falls into this category, set this to true.
197     *
198     * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
199     */
200    public void setHasFixedSize(boolean hasFixedSize) {
201        mHasFixedSize = hasFixedSize;
202    }
203
204    /**
205     * @return true if the app has specified that changes in adapter content cannot change
206     * the size of the RecyclerView itself.
207     */
208    public boolean hasFixedSize() {
209        return mHasFixedSize;
210    }
211
212    /**
213     * Set a new adapter to provide child views on demand.
214     *
215     * @param adapter The new adapter to set, or null to set no adapter.
216     */
217    public void setAdapter(Adapter adapter) {
218        if (mAdapter != null) {
219            mAdapter.unregisterAdapterDataObserver(mObserver);
220        }
221        final Adapter oldAdapter = adapter;
222        mAdapter = adapter;
223        if (adapter != null) {
224            adapter.registerAdapterDataObserver(mObserver);
225        }
226        if (mLayout != null) {
227            mLayout.onAdapterChanged();
228        }
229        mRecycler.onAdapterChanged(oldAdapter, mAdapter);
230        requestLayout();
231    }
232
233    /**
234     * Retrieves the previously set adapter or null if no adapter is set.
235     *
236     * @return The previously set adapter
237     * @see #setAdapter(Adapter)
238     */
239    public Adapter getAdapter() {
240        return mAdapter;
241    }
242
243    /**
244     * Register a listener that will be notified whenever a child view is recycled.
245     *
246     * <p>This listener will be called when a LayoutManager or the RecyclerView decides
247     * that a child view is no longer needed. If an application associates expensive
248     * or heavyweight data with item views, this may be a good place to release
249     * or free those resources.</p>
250     *
251     * @param listener Listener to register, or null to clear
252     */
253    public void setRecyclerListener(RecyclerListener listener) {
254        mRecyclerListener = listener;
255    }
256
257    /**
258     * Set the {@link LayoutManager} that this RecyclerView will use.
259     *
260     * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
261     * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
262     * layout arrangements for child views. These arrangements are controlled by the
263     * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
264     *
265     * <p>Several default strategies are provided for common uses such as lists and grids.</p>
266     *
267     * @param layout LayoutManager to use
268     */
269    public void setLayoutManager(LayoutManager layout) {
270        if (layout == mLayout) {
271            return;
272        }
273
274        mRecycler.clear();
275        removeAllViews();
276        if (mLayout != null) {
277            if (mIsAttached) {
278                mLayout.onDetachedFromWindow(this);
279            }
280            mLayout.mRecyclerView = null;
281        }
282        mLayout = layout;
283        if (layout != null) {
284            if (layout.mRecyclerView != null) {
285                throw new IllegalArgumentException("LayoutManager " + layout +
286                        " is already attached to a RecyclerView: " + layout.mRecyclerView);
287            }
288            layout.mRecyclerView = this;
289            if (mIsAttached) {
290                mLayout.onAttachedToWindow(this);
291            }
292        }
293        requestLayout();
294    }
295
296    /**
297     * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
298     * if no pool is set for this view a new one will be created. See
299     * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
300     *
301     * @return The pool used to store recycled item views for reuse.
302     * @see #setRecycledViewPool(RecycledViewPool)
303     */
304    public RecycledViewPool getRecycledViewPool() {
305        return mRecycler.getRecycledViewPool();
306    }
307
308    /**
309     * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
310     * This can be useful if you have multiple RecyclerViews with adapters that use the same
311     * view types, for example if you have several data sets with the same kinds of item views
312     * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
313     *
314     * @param pool Pool to set. If this parameter is null a new pool will be created and used.
315     */
316    public void setRecycledViewPool(RecycledViewPool pool) {
317        mRecycler.setRecycledViewPool(pool);
318    }
319
320    /**
321     * Return the current scrolling state of the RecyclerView.
322     *
323     * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
324     * {@link #SCROLL_STATE_SETTLING}
325     */
326    public int getScrollState() {
327        return mScrollState;
328    }
329
330    private void setScrollState(int state) {
331        if (state == mScrollState) {
332            return;
333        }
334        mScrollState = state;
335        if (state != SCROLL_STATE_SETTLING) {
336            mViewFlinger.stop();
337        }
338        if (mScrollListener != null) {
339            mScrollListener.onScrollStateChanged(state);
340        }
341    }
342
343    /**
344     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
345     * affect both measurement and drawing of individual item views.
346     *
347     * <p>Item decorations are ordered. Decorations placed earlier in the list will
348     * be run/queried/drawn first for their effects on item views. Padding added to views
349     * will be nested; a padding added by an earlier decoration will mean further
350     * item decorations in the list will be asked to draw/pad within the previous decoration's
351     * given area.</p>
352     *
353     * @param decor Decoration to add
354     * @param index Position in the decoration chain to insert this decoration at. If this value
355     *              is negative the decoration will be added at the end.
356     */
357    public void addItemDecoration(ItemDecoration decor, int index) {
358        if (mItemDecorations.isEmpty()) {
359            setWillNotDraw(false);
360        }
361        if (index < 0) {
362            mItemDecorations.add(decor);
363        } else {
364            mItemDecorations.add(index, decor);
365        }
366        invalidate();
367
368        // TODO Refresh layout for affected views
369    }
370
371    /**
372     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
373     * affect both measurement and drawing of individual item views.
374     *
375     * <p>Item decorations are ordered. Decorations placed earlier in the list will
376     * be run/queried/drawn first for their effects on item views. Padding added to views
377     * will be nested; a padding added by an earlier decoration will mean further
378     * item decorations in the list will be asked to draw/pad within the previous decoration's
379     * given area.</p>
380     *
381     * @param decor Decoration to add
382     */
383    public void addItemDecoration(ItemDecoration decor) {
384        addItemDecoration(decor, -1);
385    }
386
387    /**
388     * Remove an {@link ItemDecoration} from this RecyclerView.
389     *
390     * <p>The given decoration will no longer impact the measurement and drawing of
391     * item views.</p>
392     *
393     * @param decor Decoration to remove
394     * @see #addItemDecoration(ItemDecoration)
395     */
396    public void removeItemDecoration(ItemDecoration decor) {
397        mItemDecorations.remove(decor);
398        if (mItemDecorations.isEmpty()) {
399            setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
400        }
401        invalidate();
402
403        // TODO: Refresh layout for affected views
404    }
405
406    /**
407     * Set a listener that will be notified of any changes in scroll state or position.
408     *
409     * @param listener Listener to set or null to clear
410     */
411    public void setOnScrollListener(OnScrollListener listener) {
412        mScrollListener = listener;
413    }
414
415    @Override
416    public void scrollTo(int x, int y) {
417        throw new UnsupportedOperationException(
418                "RecyclerView does not support scrolling to an absolute position.");
419    }
420
421    @Override
422    public void scrollBy(int x, int y) {
423        if (mLayout == null) {
424            throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
425                    "Call setLayoutManager with a non-null argument.");
426        }
427        final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
428        final boolean canScrollVertical = mLayout.canScrollVertically();
429        if (canScrollHorizontal || canScrollVertical) {
430            scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0);
431        }
432    }
433
434    /**
435     * Does not perform bounds checking. Used by internal methods that have already validated input.
436     */
437    void scrollByInternal(int x, int y) {
438        int overscrollX = 0, overscrollY = 0;
439        eatRequestLayout();
440        if (x != 0) {
441            final int hresult = mLayout.scrollHorizontallyBy(x, getAdapter(), mRecycler);
442            overscrollX = x - hresult;
443        }
444        if (y != 0) {
445            final int vresult = mLayout.scrollVerticallyBy(y, getAdapter(), mRecycler);
446            overscrollY = y - vresult;
447        }
448        resumeRequestLayout(false);
449        pullGlows(overscrollX, overscrollY);
450        if (mScrollListener != null && (x != 0 || y != 0)) {
451            mScrollListener.onScrolled(x, y);
452        }
453    }
454
455    void eatRequestLayout() {
456        if (!mEatRequestLayout) {
457            mEatRequestLayout = true;
458            mLayoutRequestEaten = false;
459        }
460    }
461
462    void resumeRequestLayout(boolean performLayoutChildren) {
463        if (mEatRequestLayout) {
464            if (performLayoutChildren && mLayoutRequestEaten &&
465                    mLayout != null && mAdapter != null) {
466                layoutChildren();
467            }
468            mEatRequestLayout = false;
469            mLayoutRequestEaten = false;
470        }
471    }
472
473    /**
474     * Animate a scroll by the given amount of pixels along either axis.
475     *
476     * @param dx Pixels to scroll horizontally
477     * @param dy Pixels to scroll vertically
478     */
479    public void smoothScrollBy(int dx, int dy) {
480        if (dx != 0 || dy != 0) {
481            mViewFlinger.smoothScrollBy(dx, dy);
482        }
483    }
484
485    /**
486     * Begin a standard fling with an initial velocity along each axis in pixels per second.
487     * If the velocity given is below the system-defined minimum this method will return false
488     * and no fling will occur.
489     *
490     * @param velocityX Initial horizontal velocity in pixels per second
491     * @param velocityY Initial vertical velocity in pixels per second
492     * @return true if the fling was started, false if the velocity was too low to fling
493     */
494    public boolean fling(int velocityX, int velocityY) {
495        if (Math.abs(velocityX) < mMinFlingVelocity) {
496            velocityX = 0;
497        }
498        if (Math.abs(velocityY) < mMinFlingVelocity) {
499            velocityY = 0;
500        }
501        velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
502        velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
503        if (velocityX != 0 || velocityY != 0) {
504            mViewFlinger.fling(velocityX, velocityY);
505            return true;
506        }
507        return false;
508    }
509
510    /**
511     * Apply a pull to relevant overscroll glow effects
512     */
513    private void pullGlows(int overscrollX, int overscrollY) {
514        if (overscrollX < 0) {
515            if (mLeftGlow == null) {
516                mLeftGlow = new EdgeEffectCompat(getContext());
517                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
518                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
519            }
520            mLeftGlow.onPull(-overscrollX / (float) getWidth());
521        } else if (overscrollX > 0) {
522            if (mRightGlow == null) {
523                mRightGlow = new EdgeEffectCompat(getContext());
524                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
525                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
526            }
527            mRightGlow.onPull(overscrollX / (float) getWidth());
528        }
529
530        if (overscrollY < 0) {
531            if (mTopGlow == null) {
532                mTopGlow = new EdgeEffectCompat(getContext());
533                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
534                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
535            }
536            mTopGlow.onPull(-overscrollY / (float) getHeight());
537        } else if (overscrollY > 0) {
538            if (mBottomGlow == null) {
539                mBottomGlow = new EdgeEffectCompat(getContext());
540                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
541                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
542            }
543            mBottomGlow.onPull(overscrollY / (float) getHeight());
544        }
545
546        if (overscrollX != 0 || overscrollY != 0) {
547            ViewCompat.postInvalidateOnAnimation(this);
548        }
549    }
550
551    private void releaseGlows() {
552        boolean needsInvalidate = false;
553        if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease();
554        if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease();
555        if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease();
556        if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease();
557        if (needsInvalidate) {
558            ViewCompat.postInvalidateOnAnimation(this);
559        }
560    }
561
562    void absorbGlows(int velocityX, int velocityY) {
563        if (velocityX < 0) {
564            if (mLeftGlow == null) {
565                mLeftGlow = new EdgeEffectCompat(getContext());
566                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
567                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
568            }
569            mLeftGlow.onAbsorb(-velocityX);
570        } else if (velocityX > 0) {
571            if (mRightGlow == null) {
572                mRightGlow = new EdgeEffectCompat(getContext());
573                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
574                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
575            }
576            mRightGlow.onAbsorb(velocityX);
577        }
578
579        if (velocityY < 0) {
580            if (mTopGlow == null) {
581                mTopGlow = new EdgeEffectCompat(getContext());
582                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
583                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
584            }
585            mTopGlow.onAbsorb(-velocityY);
586        } else if (velocityY > 0) {
587            if (mBottomGlow == null) {
588                mBottomGlow = new EdgeEffectCompat(getContext());
589                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
590                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
591            }
592            mBottomGlow.onAbsorb(velocityY);
593        }
594
595        if (velocityX != 0 || velocityY != 0) {
596            ViewCompat.postInvalidateOnAnimation(this);
597        }
598    }
599
600    // Focus handling
601
602    @Override
603    public View focusSearch(View focused, int direction) {
604        final FocusFinder ff = FocusFinder.getInstance();
605        View result = ff.findNextFocus(this, focused, direction);
606        if (result == null) {
607            eatRequestLayout();
608            result = mLayout.onFocusSearchFailed(focused, direction, getAdapter(), mRecycler);
609            resumeRequestLayout(false);
610        }
611        return result != null ? result : super.focusSearch(focused, direction);
612    }
613
614    @Override
615    public void requestChildFocus(View child, View focused) {
616        if (!mLayout.onRequestChildFocus(this, child, focused)) {
617            mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
618            offsetDescendantRectToMyCoords(focused, mTempRect);
619            offsetRectIntoDescendantCoords(child, mTempRect);
620            requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
621        }
622        super.requestChildFocus(child, focused);
623    }
624
625    @Override
626    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
627        return mLayout.requestChildRectangleOnScreen(child, rect, immediate);
628    }
629
630    @Override
631    protected void onAttachedToWindow() {
632        super.onAttachedToWindow();
633        mIsAttached = true;
634        mFirstLayoutComplete = false;
635        if (mLayout != null) {
636            mLayout.onAttachedToWindow(this);
637        }
638    }
639
640    @Override
641    protected void onDetachedFromWindow() {
642        super.onDetachedFromWindow();
643        mFirstLayoutComplete = false;
644
645        mViewFlinger.stop();
646        // TODO Mark what our target position was if relevant, then we can jump there
647        // on reattach.
648
649        mIsAttached = false;
650        if (mLayout != null) {
651            mLayout.onDetachedFromWindow(this);
652        }
653    }
654
655    /**
656     * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
657     * to child views or this view's standard scrolling behavior.
658     *
659     * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
660     * returns true from
661     * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
662     * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
663     * for each incoming MotionEvent until the end of the gesture.</p>
664     *
665     * @param listener Listener to add
666     */
667    public void addOnItemTouchListener(OnItemTouchListener listener) {
668        mOnItemTouchListeners.add(listener);
669    }
670
671    /**
672     * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
673     *
674     * @param listener Listener to remove
675     */
676    public void removeOnItemTouchListener(OnItemTouchListener listener) {
677        mOnItemTouchListeners.remove(listener);
678        if (mActiveOnItemTouchListener == listener) {
679            mActiveOnItemTouchListener = null;
680        }
681    }
682
683    private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
684        final int action = e.getAction();
685        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
686            mActiveOnItemTouchListener = null;
687        }
688
689        final int listenerCount = mOnItemTouchListeners.size();
690        for (int i = 0; i < listenerCount; i++) {
691            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
692            if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
693                mActiveOnItemTouchListener = listener;
694                return true;
695            }
696        }
697        return false;
698    }
699
700    private boolean dispatchOnItemTouch(MotionEvent e) {
701        if (mActiveOnItemTouchListener != null) {
702            final int action = e.getAction();
703            if (action == MotionEvent.ACTION_DOWN) {
704                // Stale state from a previous gesture, we're starting a new one. Clear it.
705                mActiveOnItemTouchListener = null;
706            } else {
707                mActiveOnItemTouchListener.onTouchEvent(this, e);
708                if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
709                    // Clean up for the next gesture.
710                    mActiveOnItemTouchListener = null;
711                }
712                return true;
713            }
714        }
715
716        final int listenerCount = mOnItemTouchListeners.size();
717        for (int i = 0; i < listenerCount; i++) {
718            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
719            if (listener.onInterceptTouchEvent(this, e)) {
720                mActiveOnItemTouchListener = listener;
721                return true;
722            }
723        }
724        return false;
725    }
726
727    @Override
728    public boolean onInterceptTouchEvent(MotionEvent e) {
729        if (dispatchOnItemTouchIntercept(e)) {
730            cancelTouch();
731            return true;
732        }
733
734        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
735        final boolean canScrollVertically = mLayout.canScrollVertically();
736
737        if (mVelocityTracker == null) {
738            mVelocityTracker = VelocityTracker.obtain();
739        }
740        mVelocityTracker.addMovement(e);
741
742        final int action = MotionEventCompat.getActionMasked(e);
743        final int actionIndex = MotionEventCompat.getActionIndex(e);
744
745        switch (action) {
746            case MotionEvent.ACTION_DOWN:
747                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
748                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
749                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
750
751                if (mScrollState == SCROLL_STATE_SETTLING) {
752                    getParent().requestDisallowInterceptTouchEvent(true);
753                    setScrollState(SCROLL_STATE_DRAGGING);
754                }
755                break;
756
757            case MotionEventCompat.ACTION_POINTER_DOWN:
758                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
759                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
760                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
761                break;
762
763            case MotionEvent.ACTION_MOVE: {
764                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
765                if (index < 0) {
766                    Log.e(TAG, "Error processing scroll; pointer index for id " +
767                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
768                    return false;
769                }
770
771                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
772                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
773                if (mScrollState != SCROLL_STATE_DRAGGING) {
774                    final int dx = x - mInitialTouchX;
775                    final int dy = y - mInitialTouchY;
776                    boolean startScroll = false;
777                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
778                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
779                        startScroll = true;
780                    }
781                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
782                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
783                        startScroll = true;
784                    }
785                    if (startScroll) {
786                        getParent().requestDisallowInterceptTouchEvent(true);
787                        setScrollState(SCROLL_STATE_DRAGGING);
788                    }
789                }
790            } break;
791
792            case MotionEventCompat.ACTION_POINTER_UP: {
793                onPointerUp(e);
794            } break;
795
796            case MotionEvent.ACTION_UP: {
797                mVelocityTracker.clear();
798            } break;
799
800            case MotionEvent.ACTION_CANCEL: {
801                cancelTouch();
802            }
803        }
804        return mScrollState == SCROLL_STATE_DRAGGING;
805    }
806
807    @Override
808    public boolean onTouchEvent(MotionEvent e) {
809        if (dispatchOnItemTouch(e)) {
810            cancelTouch();
811            return true;
812        }
813
814        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
815        final boolean canScrollVertically = mLayout.canScrollVertically();
816
817        if (mVelocityTracker == null) {
818            mVelocityTracker = VelocityTracker.obtain();
819        }
820        mVelocityTracker.addMovement(e);
821
822        final int action = MotionEventCompat.getActionMasked(e);
823        final int actionIndex = MotionEventCompat.getActionIndex(e);
824
825        switch (action) {
826            case MotionEvent.ACTION_DOWN: {
827                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
828                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
829                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
830            } break;
831
832            case MotionEventCompat.ACTION_POINTER_DOWN: {
833                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
834                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
835                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
836            } break;
837
838            case MotionEvent.ACTION_MOVE: {
839                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
840                if (index < 0) {
841                    Log.e(TAG, "Error processing scroll; pointer index for id " +
842                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
843                    return false;
844                }
845
846                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
847                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
848                if (mScrollState != SCROLL_STATE_DRAGGING) {
849                    final int dx = x - mInitialTouchX;
850                    final int dy = y - mInitialTouchY;
851                    boolean startScroll = false;
852                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
853                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
854                        startScroll = true;
855                    }
856                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
857                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
858                        startScroll = true;
859                    }
860                    if (startScroll) {
861                        getParent().requestDisallowInterceptTouchEvent(true);
862                        setScrollState(SCROLL_STATE_DRAGGING);
863                    }
864                }
865                if (mScrollState == SCROLL_STATE_DRAGGING) {
866                    final int dx = x - mLastTouchX;
867                    final int dy = y - mLastTouchY;
868                    scrollByInternal(canScrollHorizontally ? -dx : 0,
869                            canScrollVertically ? -dy : 0);
870                }
871                mLastTouchX = x;
872                mLastTouchY = y;
873            } break;
874
875            case MotionEventCompat.ACTION_POINTER_UP: {
876                onPointerUp(e);
877            } break;
878
879            case MotionEvent.ACTION_UP: {
880                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
881                final float xvel = canScrollHorizontally ?
882                        -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
883                final float yvel = canScrollVertically ?
884                        -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
885                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
886                    setScrollState(SCROLL_STATE_IDLE);
887                }
888
889                mVelocityTracker.clear();
890                releaseGlows();
891            } break;
892
893            case MotionEvent.ACTION_CANCEL: {
894                cancelTouch();
895            } break;
896        }
897
898        return true;
899    }
900
901    private void cancelTouch() {
902        mVelocityTracker.clear();
903        releaseGlows();
904        setScrollState(SCROLL_STATE_IDLE);
905    }
906
907    private void onPointerUp(MotionEvent e) {
908        final int actionIndex = MotionEventCompat.getActionIndex(e);
909        if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) {
910            // Pick a new pointer to pick up the slack.
911            final int newIndex = actionIndex == 0 ? 1 : 0;
912            mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex);
913            mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f);
914            mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f);
915        }
916    }
917
918    @Override
919    protected void onMeasure(int widthSpec, int heightSpec) {
920        if (mAdapterUpdateDuringMeasure) {
921            eatRequestLayout();
922            updateChildViews();
923            resumeRequestLayout(false);
924        }
925
926        final int widthMode = MeasureSpec.getMode(widthSpec);
927        final int heightMode = MeasureSpec.getMode(heightSpec);
928        final int widthSize = MeasureSpec.getSize(widthSpec);
929        final int heightSize = MeasureSpec.getSize(heightSpec);
930
931        if (!mHasFixedSize || widthMode == MeasureSpec.UNSPECIFIED ||
932                heightMode == MeasureSpec.UNSPECIFIED) {
933            throw new IllegalStateException("Non-fixed sizes not yet supported");
934        }
935
936        if (mLeftGlow != null) mLeftGlow.setSize(heightSize, widthSize);
937        if (mTopGlow != null) mTopGlow.setSize(widthSize, heightSize);
938        if (mRightGlow != null) mRightGlow.setSize(heightSize, widthSize);
939        if (mBottomGlow != null) mBottomGlow.setSize(widthSize, heightSize);
940
941        setMeasuredDimension(widthSize, heightSize);
942    }
943
944    @Override
945    protected void onLayout(boolean changed, int l, int t, int r, int b) {
946        if (mAdapter == null) {
947            Log.e(TAG, "No adapter attached; skipping layout");
948            return;
949        }
950        eatRequestLayout();
951        layoutChildren();
952        resumeRequestLayout(false);
953        mFirstLayoutComplete = true;
954    }
955
956    @Override
957    public void requestLayout() {
958        if (!mEatRequestLayout) {
959            super.requestLayout();
960        } else {
961            mLayoutRequestEaten = true;
962        }
963    }
964
965    void layoutChildren() {
966        mLayout.layoutChildren(mAdapter, mRecycler, mStructureChanged);
967        mStructureChanged = false;
968    }
969
970    @Override
971    public void draw(Canvas c) {
972        super.draw(c);
973
974        final int count = mItemDecorations.size();
975        for (int i = 0; i < count; i++) {
976            mItemDecorations.get(i).onDrawOver(c);
977        }
978
979        boolean needsInvalidate = false;
980        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
981            final int restore = c.save();
982            c.rotate(270);
983            c.translate(-getHeight() + getPaddingTop(), 0);
984            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
985            c.restoreToCount(restore);
986        }
987        if (mTopGlow != null && !mTopGlow.isFinished()) {
988            c.translate(getPaddingLeft(), getPaddingTop());
989            needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
990        }
991        if (mRightGlow != null && !mRightGlow.isFinished()) {
992            final int restore = c.save();
993            final int width = getWidth();
994
995            c.rotate(90);
996            c.translate(-getPaddingTop(), -width);
997            needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
998            c.restoreToCount(restore);
999        }
1000        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
1001            final int restore = c.save();
1002            c.rotate(180);
1003            c.translate(-getWidth() + getPaddingLeft(), -getHeight() + getPaddingTop());
1004            needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
1005            c.restoreToCount(restore);
1006        }
1007
1008        if (needsInvalidate) {
1009            ViewCompat.postInvalidateOnAnimation(this);
1010        }
1011    }
1012
1013    @Override
1014    public void onDraw(Canvas c) {
1015        super.onDraw(c);
1016
1017        final int count = mItemDecorations.size();
1018        for (int i = 0; i < count; i++) {
1019            mItemDecorations.get(i).onDraw(c);
1020        }
1021    }
1022
1023    @Override
1024    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1025        return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
1026    }
1027
1028    @Override
1029    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1030        if (mLayout == null) {
1031            throw new IllegalStateException("RecyclerView has no LayoutManager");
1032        }
1033        return mLayout.generateDefaultLayoutParams();
1034    }
1035
1036    @Override
1037    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1038        if (mLayout == null) {
1039            throw new IllegalStateException("RecyclerView has no LayoutManager");
1040        }
1041        return mLayout.generateLayoutParams(getContext(), attrs);
1042    }
1043
1044    @Override
1045    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1046        if (mLayout == null) {
1047            throw new IllegalStateException("RecyclerView has no LayoutManager");
1048        }
1049        return mLayout.generateLayoutParams(p);
1050    }
1051
1052    void updateChildViews() {
1053        final int opCount = mPendingUpdates.size();
1054        for (int i = 0; i < opCount; i++) {
1055            final UpdateOp op = mPendingUpdates.get(i);
1056            switch (op.cmd) {
1057                case UpdateOp.ADD:
1058                    if (DEBUG) {
1059                        Log.d(TAG, "UpdateOp.ADD start=" + op.positionStart + " count=" +
1060                                op.itemCount);
1061                    }
1062                    offsetPositionRecordsForInsert(op.positionStart, op.itemCount);
1063
1064                    // TODO Animate it in
1065                    break;
1066                case UpdateOp.REMOVE:
1067                    if (DEBUG) {
1068                        Log.d(TAG, "UpdateOp.REMOVE start=" + op.positionStart + " count=" +
1069                                op.itemCount);
1070                    }
1071                    offsetPositionRecordsForRemove(op.positionStart, op.itemCount);
1072
1073                    // TODO Animate it away
1074                    break;
1075                case UpdateOp.UPDATE:
1076                    viewRangeUpdate(op.positionStart, op.itemCount);
1077                    break;
1078            }
1079            recycleUpdateOp(op);
1080        }
1081        mPendingUpdates.clear();
1082    }
1083
1084    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
1085        boolean needsLayout = false;
1086        final int childCount = getChildCount();
1087        for (int i = 0; i < childCount; i++) {
1088            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1089            if (holder != null && holder.mPosition >= positionStart) {
1090                if (DEBUG) {
1091                    Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " +
1092                            holder + " now at position " + (holder.mPosition + itemCount));
1093                }
1094                holder.mPosition += itemCount;
1095                needsLayout = true;
1096                mStructureChanged = true;
1097            }
1098        }
1099        mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
1100        if (needsLayout) {
1101            requestLayout();
1102        }
1103    }
1104
1105    void offsetPositionRecordsForRemove(int positionStart, int itemCount) {
1106        boolean needsLayout = false;
1107        final int positionEnd = positionStart + itemCount;
1108        final int childCount = getChildCount();
1109        for (int i = 0; i < childCount; i++) {
1110            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1111            if (holder != null) {
1112                if (holder.mPosition >= positionEnd) {
1113                    if (DEBUG) {
1114                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
1115                                " holder " + holder + " now at position " +
1116                                (holder.mPosition - itemCount));
1117                    }
1118                    holder.mPosition -= itemCount;
1119                    needsLayout = true;
1120                    mStructureChanged = true;
1121                } else if (holder.mPosition >= positionStart) {
1122                    if (DEBUG) {
1123                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
1124                                " holder " + holder + " now REMOVED");
1125                    }
1126                    holder.addFlags(ViewHolder.FLAG_REMOVED);
1127                    needsLayout = true;
1128                    mStructureChanged = true;
1129                }
1130            }
1131        }
1132        mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount);
1133        if (needsLayout) {
1134            requestLayout();
1135        }
1136    }
1137
1138    /**
1139     * Rebind existing views for the given range, or create as needed.
1140     *
1141     * @param positionStart Adapter position to start at
1142     * @param itemCount Number of views that must explicitly be rebound
1143     */
1144    void viewRangeUpdate(int positionStart, int itemCount) {
1145        final int childCount = getChildCount();
1146        final int positionEnd = positionStart + itemCount;
1147
1148        for (int i = 0; i < childCount; i++) {
1149            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1150            if (holder == null) {
1151                continue;
1152            }
1153
1154            final int position = holder.getPosition();
1155            if (position >= positionStart && position < positionEnd) {
1156                holder.addFlags(ViewHolder.FLAG_UPDATE);
1157                // Binding an attached view will request a layout if needed.
1158                mAdapter.bindViewHolder(holder, holder.getPosition());
1159            }
1160        }
1161        mRecycler.viewRangeUpdate(positionStart, itemCount);
1162    }
1163
1164    /**
1165     * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
1166     * data change event.
1167     */
1168    void markKnownViewsInvalid() {
1169        final int childCount = getChildCount();
1170
1171        for (int i = 0; i < childCount; i++) {
1172            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1173            if (holder != null) {
1174                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
1175            }
1176        }
1177        mRecycler.markKnownViewsInvalid();
1178    }
1179
1180    /**
1181     * Schedule an update of data from the adapter to occur on the next frame.
1182     * On newer platform versions this happens via the postOnAnimation mechanism and RecyclerView
1183     * attempts to avoid relayouts if possible.
1184     * On older platform versions the RecyclerView requests a layout the same way ListView does.
1185     */
1186    void postAdapterUpdate(UpdateOp op) {
1187        mPendingUpdates.add(op);
1188        if (mPendingUpdates.size() == 1) {
1189            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
1190                ViewCompat.postOnAnimation(this, mUpdateChildViewsRunnable);
1191            } else {
1192                mAdapterUpdateDuringMeasure = true;
1193                requestLayout();
1194            }
1195        }
1196    }
1197
1198    ViewHolder getChildViewHolder(View child) {
1199        if (child == null) {
1200            return null;
1201        }
1202        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
1203    }
1204
1205    /**
1206     * Return the adapter position that the given child view corresponds to.
1207     *
1208     * @param child Child View to query
1209     * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
1210     */
1211    public int getChildPosition(View child) {
1212        final ViewHolder holder = getChildViewHolder(child);
1213        return holder != null ? holder.getPosition() : NO_POSITION;
1214    }
1215
1216    /**
1217     * Return the stable item id that the given child view corresponds to.
1218     *
1219     * @param child Child View to query
1220     * @return Item id corresponding to the given view or {@link #NO_ID}
1221     */
1222    public long getChildItemId(View child) {
1223        if (mAdapter == null || !mAdapter.hasStableIds()) {
1224            return NO_ID;
1225        }
1226        final ViewHolder holder = getChildViewHolder(child);
1227        return holder != null ? holder.getItemId() : NO_ID;
1228    }
1229
1230    /**
1231     * Return the ViewHolder for the item in the given position of the data set.
1232     *
1233     * @param position The position of the item in the data set of the adapter
1234     * @return The ViewHolder at <code>position</code>
1235     */
1236    public ViewHolder findViewHolderForPosition(int position) {
1237        final int childCount = getChildCount();
1238        for (int i = 0; i < childCount; i++) {
1239            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1240            if (holder != null && holder.getPosition() == position) {
1241                return holder;
1242            }
1243        }
1244        return mRecycler.findViewHolderForPosition(position);
1245    }
1246
1247    /**
1248     * Return the ViewHolder for the item with the given id. The RecyclerView must
1249     * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
1250     * return a non-null value.
1251     *
1252     * @param id The id for the requested item
1253     * @return The ViewHolder with the given <code>id</code>, of null if there
1254     * is no such item.
1255     */
1256    public ViewHolder findViewHolderForItemId(long id) {
1257        final int childCount = getChildCount();
1258        for (int i = 0; i < childCount; i++) {
1259            final ViewHolder holder = getChildViewHolder(getChildAt(i));
1260            if (holder != null && holder.getItemId() == id) {
1261                return holder;
1262            }
1263        }
1264        return mRecycler.findViewHolderForItemId(id);
1265    }
1266
1267    /**
1268     * Return the ViewHolder for the child view positioned underneath the coordinates (x, y).
1269     *
1270     * @param x Horizontal position in pixels to search
1271     * @param y Vertical position in pixels to search
1272     * @return The ViewHolder for the child under (x, y) or null if no child is found
1273     */
1274    public ViewHolder findViewHolderForChildUnder(int x, int y) {
1275        final int count = getChildCount();
1276        for (int i = count; i >= 0; i--) {
1277            final View child = getChildAt(i);
1278            if (x >= child.getLeft() && x <= child.getRight() && y >= child.getTop() &&
1279                    y <= child.getBottom()) {
1280                return getChildViewHolder(child);
1281            }
1282        }
1283        return null;
1284    }
1285
1286    /**
1287     * Return the ViewHolder for the child view of the RecyclerView that is at the
1288     * given index.
1289     *
1290     * @param childIndex The index of the child in the RecyclerView's child list.
1291     * @return The ViewHolder for the given <code>childIndex</code>
1292     */
1293    public ViewHolder getViewHolderForChildAt(int childIndex) {
1294        return getChildViewHolder(getChildAt(childIndex));
1295    }
1296
1297    /**
1298     * Offset the bounds of all child views by <code>dy</code> pixels.
1299     * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
1300     *
1301     * @param dy Vertical pixel offset to apply to the bounds of all child views
1302     */
1303    public void offsetChildrenVertical(int dy) {
1304        final int childCount = getChildCount();
1305        for (int i = 0; i < childCount; i++) {
1306            getChildAt(i).offsetTopAndBottom(dy);
1307        }
1308    }
1309
1310    /**
1311     * Offset the bounds of all child views by <code>dx</code> pixels.
1312     * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
1313     *
1314     * @param dx Horizontal pixel offset to apply to the bounds of all child views
1315     */
1316    public void offsetChildrenHorizontal(int dx) {
1317        final int childCount = getChildCount();
1318        for (int i = 0; i < childCount; i++) {
1319            getChildAt(i).offsetLeftAndRight(dx);
1320        }
1321    }
1322
1323    private class ViewFlinger implements Runnable {
1324        private int mLastFlingX;
1325        private int mLastFlingY;
1326        private ScrollerCompat mScroller;
1327        private Interpolator mInterpolator = sQuinticInterpolator;
1328
1329        public ViewFlinger() {
1330            mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator);
1331        }
1332
1333        @Override
1334        public void run() {
1335            if (mScroller.computeScrollOffset()) {
1336                final int x = mScroller.getCurrX();
1337                final int y = mScroller.getCurrY();
1338                final int dx = x - mLastFlingX;
1339                final int dy = y - mLastFlingY;
1340                mLastFlingX = x;
1341                mLastFlingY = y;
1342
1343                int overscrollX = 0, overscrollY = 0;
1344                eatRequestLayout();
1345                if (dx != 0) {
1346                    final int hresult = mLayout.scrollHorizontallyBy(dx, getAdapter(), mRecycler);
1347                    overscrollX = dx - hresult;
1348                }
1349                if (dy != 0) {
1350                    final int vresult = mLayout.scrollVerticallyBy(dy, getAdapter(), mRecycler);
1351                    overscrollY = dy - vresult;
1352                }
1353                resumeRequestLayout(false);
1354
1355                if (overscrollX != 0 || overscrollY != 0) {
1356                    final int vel = (int) mScroller.getCurrVelocity();
1357
1358                    int velX = 0;
1359                    if (overscrollX != x) {
1360                        velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
1361                    }
1362
1363                    int velY = 0;
1364                    if (overscrollY != y) {
1365                        velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
1366                    }
1367
1368                    absorbGlows(velX, velY);
1369                    if ((velX != 0 || overscrollX == x || mScroller.getFinalX() == 0) &&
1370                            (velY != 0 || overscrollY == y || mScroller.getFinalY() == 0)) {
1371                        mScroller.abortAnimation();
1372                    }
1373                }
1374
1375                if (mScrollListener != null && (x != 0 || y != 0)) {
1376                    mScrollListener.onScrolled(dx, dy);
1377                }
1378
1379                if (mScroller.isFinished()) {
1380                    setScrollState(SCROLL_STATE_IDLE);
1381                } else {
1382                    ViewCompat.postOnAnimation(RecyclerView.this, this);
1383                }
1384            }
1385        }
1386
1387        public void fling(int velocityX, int velocityY) {
1388            setScrollState(SCROLL_STATE_SETTLING);
1389            mLastFlingX = mLastFlingY = 0;
1390            mScroller.fling(0, 0, velocityX, velocityY,
1391                    Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
1392            ViewCompat.postOnAnimation(RecyclerView.this, this);
1393        }
1394
1395        public void smoothScrollBy(int dx, int dy) {
1396            smoothScrollBy(dx, dy, 0, 0);
1397        }
1398
1399        public void smoothScrollBy(int dx, int dy, int vx, int vy) {
1400            smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
1401        }
1402
1403        private float distanceInfluenceForSnapDuration(float f) {
1404            f -= 0.5f; // center the values about 0.
1405            f *= 0.3f * Math.PI / 2.0f;
1406            return (float) Math.sin(f);
1407        }
1408
1409        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
1410            final int absDx = Math.abs(dx);
1411            final int absDy = Math.abs(dy);
1412            final boolean horizontal = absDx > absDy;
1413            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
1414            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
1415            final int containerSize = horizontal ? getWidth() : getHeight();
1416            final int halfContainerSize = containerSize / 2;
1417            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
1418            final float distance = halfContainerSize + halfContainerSize *
1419                    distanceInfluenceForSnapDuration(distanceRatio);
1420
1421            final int duration;
1422            if (velocity > 0) {
1423                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1424            } else {
1425                float absDelta = (float) (horizontal ? absDx : absDy);
1426                duration = (int) (((absDelta / containerSize) + 1) * 300);
1427            }
1428            return Math.min(duration, MAX_SCROLL_DURATION);
1429        }
1430
1431        public void smoothScrollBy(int dx, int dy, int duration) {
1432            smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
1433        }
1434
1435        public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
1436            if (mInterpolator != interpolator) {
1437                mInterpolator = interpolator;
1438                mScroller = ScrollerCompat.create(getContext(), interpolator);
1439            }
1440            setScrollState(SCROLL_STATE_SETTLING);
1441            mLastFlingX = mLastFlingY = 0;
1442            mScroller.startScroll(0, 0, dx, dy, duration);
1443            ViewCompat.postOnAnimation(RecyclerView.this, this);
1444        }
1445
1446        public void stop() {
1447            removeCallbacks(this);
1448        }
1449
1450    }
1451
1452    private class RecyclerViewDataObserver extends AdapterDataObserver {
1453        @Override
1454        public void onChanged() {
1455            if (mAdapter.hasStableIds()) {
1456                // TODO Determine what actually changed
1457                markKnownViewsInvalid();
1458                mStructureChanged = true;
1459                requestLayout();
1460            } else {
1461                markKnownViewsInvalid();
1462                mStructureChanged = true;
1463                requestLayout();
1464            }
1465        }
1466
1467        @Override
1468        public void onItemRangeChanged(int positionStart, int itemCount) {
1469            postAdapterUpdate(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
1470        }
1471
1472        @Override
1473        public void onItemRangeInserted(int positionStart, int itemCount) {
1474            postAdapterUpdate(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
1475        }
1476
1477        @Override
1478        public void onItemRangeRemoved(int positionStart, int itemCount) {
1479            postAdapterUpdate(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
1480        }
1481    }
1482
1483    public static class RecycledViewPool {
1484        private SparseArray<ArrayList<ViewHolder>> mScrap =
1485                new SparseArray<ArrayList<ViewHolder>>();
1486        private SparseIntArray mMaxScrap = new SparseIntArray();
1487        private int mAttachCount = 0;
1488
1489        private static final int DEFAULT_MAX_SCRAP = 5;
1490
1491        public void clear() {
1492            mScrap.clear();
1493        }
1494
1495        /** @deprecated No longer needed */
1496        public void reset(int typeCount) {
1497        }
1498
1499        public void setMaxRecycledViews(int viewType, int max) {
1500            mMaxScrap.put(viewType, max);
1501            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
1502            if (scrapHeap != null) {
1503                while (scrapHeap.size() > max) {
1504                    scrapHeap.remove(scrapHeap.size() - 1);
1505                }
1506            }
1507        }
1508
1509        public ViewHolder getRecycledView(int viewType) {
1510            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
1511            if (scrapHeap != null && !scrapHeap.isEmpty()) {
1512                final int index = scrapHeap.size() - 1;
1513                final ViewHolder scrap = scrapHeap.get(index);
1514                scrapHeap.remove(index);
1515                return scrap;
1516            }
1517            return null;
1518        }
1519
1520        public void putRecycledView(ViewHolder scrap) {
1521            final int viewType = scrap.getItemViewType();
1522            final ArrayList scrapHeap = getScrapHeapForType(viewType);
1523            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
1524                return;
1525            }
1526
1527            scrap.mPosition = NO_POSITION;
1528            scrap.mItemId = NO_ID;
1529            scrap.clearFlagsForSharedPool();
1530            scrapHeap.add(scrap);
1531        }
1532
1533        void attach(Adapter adapter) {
1534            mAttachCount++;
1535        }
1536
1537        void detach() {
1538            mAttachCount--;
1539        }
1540
1541
1542        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
1543            if (mAttachCount == 1) {
1544                clear();
1545            }
1546        }
1547
1548        private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
1549            ArrayList<ViewHolder> scrap = mScrap.get(viewType);
1550            if (scrap == null) {
1551                scrap = new ArrayList<ViewHolder>();
1552                mScrap.put(viewType, scrap);
1553                if (mMaxScrap.indexOfKey(viewType) < 0) {
1554                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
1555                }
1556            }
1557            return scrap;
1558        }
1559    }
1560
1561    /**
1562     * A Recycler is responsible for managing scrapped or detached item views for reuse.
1563     *
1564     * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
1565     * that has been marked for removal or reuse.</p>
1566     *
1567     * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
1568     * an adapter's data set representing the data at a given position or item ID.
1569     * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
1570     * If not, the view can be quickly reused by the LayoutManager with no further work.
1571     * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
1572     * may be repositioned by a LayoutManager without remeasurement.</p>
1573     */
1574    public final class Recycler {
1575        private final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
1576
1577        private final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
1578        private int mViewCacheMax = DEFAULT_CACHE_SIZE;
1579
1580        private RecycledViewPool mRecyclerPool;
1581
1582        private static final int DEFAULT_CACHE_SIZE = 5;
1583
1584        /**
1585         * Clear scrap views out of this recycler. Detached views contained within a
1586         * recycled view pool will remain.
1587         */
1588        public void clear() {
1589            mAttachedScrap.clear();
1590            mCachedViews.clear();
1591        }
1592
1593        /**
1594         * Set the maximum number of detached, valid views we should retain for later use.
1595         *
1596         * @param viewCount Number of views to keep before sending views to the shared pool
1597         */
1598        public void setViewCacheSize(int viewCount) {
1599            mViewCacheMax = viewCount;
1600            while (mCachedViews.size() > viewCount) {
1601                mCachedViews.remove(mCachedViews.size() - 1);
1602            }
1603        }
1604
1605        /**
1606         * @deprecated Use {@link
1607         * #getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}
1608         *             instead. This method will be removed.
1609         */
1610        public View getViewForPosition(int position) {
1611            return getViewForPosition(mAdapter, position);
1612        }
1613
1614        /**
1615         * Obtain a view initialized for the given position.
1616         *
1617         * <p>This method should be used by {@link LayoutManager} implementations to obtain
1618         * views to represent data from an {@link Adapter}.</p>
1619         *
1620         * <p>The Recycler may reuse a scrap or detached view from a shared pool if one is
1621         * available for the correct view type. If the adapter has not indicated that the
1622         * data at the given position has changed, the Recycler will attempt to hand back
1623         * a scrap view that was previously initialized for that data without rebinding.</p>
1624         *
1625         * @param adapter Adapter to use for binding and creating views
1626         * @param position Position to obtain a view for
1627         * @return A view representing the data at <code>position</code> from <code>adapter</code>
1628         */
1629        public View getViewForPosition(Adapter adapter, int position) {
1630            ViewHolder holder;
1631            final int type = adapter.getItemViewType(position);
1632            if (adapter.hasStableIds()) {
1633                final long id = adapter.getItemId(position);
1634                holder = getScrapViewForId(id, type);
1635            } else {
1636                holder = getScrapViewForPosition(position, type);
1637            }
1638
1639            if (holder == null) {
1640                holder = adapter.createViewHolder(RecyclerView.this, type);
1641                if (DEBUG) Log.d(TAG, "getViewForPosition created new ViewHolder");
1642            }
1643
1644            if (!holder.isBound() || holder.needsUpdate()) {
1645                if (DEBUG) {
1646                    Log.d(TAG, "getViewForPosition unbound holder or needs update; updating...");
1647                }
1648                adapter.bindViewHolder(holder, position);
1649            }
1650
1651            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
1652            if (lp == null) {
1653                lp = generateDefaultLayoutParams();
1654                holder.itemView.setLayoutParams(lp);
1655            } else if (!checkLayoutParams(lp)) {
1656                lp = generateLayoutParams(lp);
1657                holder.itemView.setLayoutParams(lp);
1658            }
1659            ((LayoutParams) lp).mViewHolder = holder;
1660
1661            return holder.itemView;
1662        }
1663
1664        /**
1665         * @deprecated Renamed to {@link #recycleView(android.view.View)} to cause
1666         * less confusion between temporarily detached scrap views and fully detached
1667         * recycled views. This method will be removed.
1668         */
1669        public void addDetachedScrapView(View scrap) {
1670            recycleView(scrap);
1671        }
1672
1673        /**
1674         * Recycle a detached view. The specified view will be added to a pool of views
1675         * for later rebinding and reuse.
1676         *
1677         * <p>A view must be fully detached before it may be recycled.</p>
1678         *
1679         * @param view Removed view for recycling
1680         */
1681        public void recycleView(View view) {
1682            recycleViewHolder(getChildViewHolder(view));
1683        }
1684
1685        void recycleViewHolder(ViewHolder holder) {
1686            if (holder.isScrap() || holder.itemView.getParent() != null) {
1687                throw new IllegalArgumentException(
1688                        "Scrapped or attached views may not be recycled.");
1689            }
1690
1691            // Retire oldest cached views first
1692            if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
1693                for (int i = 0; i < mCachedViews.size(); i++) {
1694                    final ViewHolder cachedView = mCachedViews.get(i);
1695                    if (cachedView.isRecyclable()) {
1696                        mCachedViews.remove(i);
1697                        getRecycledViewPool().putRecycledView(cachedView);
1698                        dispatchViewRecycled(cachedView);
1699                        break;
1700                    }
1701                }
1702            }
1703            if (mCachedViews.size() < mViewCacheMax || !holder.isRecyclable()) {
1704                mCachedViews.add(holder);
1705            } else {
1706                getRecycledViewPool().putRecycledView(holder);
1707                dispatchViewRecycled(holder);
1708            }
1709        }
1710
1711        /**
1712         * Used as a fast path for unscrapping and recycling a view during a bulk operation.
1713         * The caller must call {@link #clearScrap()} when it's done to update the recycler's
1714         * internal bookkeeping.
1715         */
1716        void quickRecycleScrapView(View view) {
1717            final ViewHolder holder = getChildViewHolder(view);
1718            holder.mScrapContainer = null;
1719            recycleViewHolder(holder);
1720        }
1721
1722        /**
1723         * @deprecated This method will be removed. Adding and removing views is the responsibility
1724         * of the LayoutManager; the Recycler will only be responsible for marking and tracking
1725         * views for reuse. This method no longer matches the definition of 'scrap'.
1726         */
1727        public void detachAndScrapView(View scrap) {
1728            if (scrap.getParent() != RecyclerView.this) {
1729                throw new IllegalArgumentException("View " + scrap + " is not attached to " +
1730                        RecyclerView.this);
1731            }
1732            mLayout.removeView(scrap);
1733            recycleView(scrap);
1734        }
1735
1736        /**
1737         * @deprecated This method will be removed. Moved to
1738         * {@link LayoutManager#detachAndScrapAttachedViews(android.support.v7.widget.RecyclerView.Recycler)}
1739         * to keep LayoutManager as the owner of attach/detach operations.
1740         */
1741        public void scrapAllViewsAttached() {
1742            mLayout.detachAndScrapAttachedViews(this);
1743        }
1744
1745        /**
1746         * Mark an attached view as scrap.
1747         *
1748         * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
1749         * for rebinding and reuse. Requests for a view for a given position may return a
1750         * reused or rebound scrap view instance.</p>
1751         *
1752         * @param view View to scrap
1753         */
1754        void scrapView(View view) {
1755            final ViewHolder holder = getChildViewHolder(view);
1756            holder.setScrapContainer(this);
1757            mAttachedScrap.add(holder);
1758        }
1759
1760        /**
1761         * Remove a previously scrapped view from the pool of eligible scrap.
1762         *
1763         * <p>This view will no longer be eligible for reuse until re-scrapped or
1764         * until it is explicitly removed and recycled.</p>
1765         */
1766        void unscrapView(ViewHolder holder) {
1767            mAttachedScrap.remove(holder);
1768        }
1769
1770        /**
1771         * @deprecated This method will be removed. Adding and removing views should be done
1772         * through the LayoutManager. Use
1773         * {@link LayoutManager#removeAndRecycleScrap(android.support.v7.widget.RecyclerView.Recycler)}
1774         * instead.
1775         */
1776        public void detachDirtyScrapViews() {
1777            mLayout.removeAndRecycleScrap(this);
1778        }
1779
1780        int getScrapCount() {
1781            return mAttachedScrap.size();
1782        }
1783
1784        View getScrapViewAt(int index) {
1785            return mAttachedScrap.get(index).itemView;
1786        }
1787
1788        void clearScrap() {
1789            mAttachedScrap.clear();
1790        }
1791
1792        ViewHolder getScrapViewForPosition(int position, int type) {
1793            final int scrapCount = mAttachedScrap.size();
1794
1795            // Try first for an exact, non-invalid match from scrap.
1796            for (int i = 0; i < scrapCount; i++) {
1797                final ViewHolder holder = mAttachedScrap.get(i);
1798                if (holder.getPosition() == position && !holder.isInvalid()) {
1799                    if (holder.getItemViewType() != type) {
1800                        Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
1801                                " wrong view type! (found " + holder.getItemViewType() +
1802                                " but expected " + type + ")");
1803                        break;
1804                    }
1805                    mAttachedScrap.remove(i);
1806                    holder.setScrapContainer(null);
1807                    if (DEBUG) {
1808                        Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
1809                            ") found exact match in scrap: " + holder);
1810                    }
1811                    return holder;
1812                }
1813            }
1814
1815            // Search in our first-level recycled view cache.
1816            final int cacheSize = mCachedViews.size();
1817            for (int i = 0; i < cacheSize; i++) {
1818                final ViewHolder holder = mCachedViews.get(i);
1819                if (holder.getPosition() == position) {
1820                    mCachedViews.remove(i);
1821                    if (holder.isInvalid() && holder.getItemViewType() != type) {
1822                        // Can't use it. We don't know where it's been.
1823                        if (DEBUG) {
1824                            Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
1825                                    ") found position match, but holder is invalid with type " +
1826                                    holder.getItemViewType());
1827                        }
1828
1829                        if (holder.isRecyclable()) {
1830                            getRecycledViewPool().putRecycledView(holder);
1831                        }
1832                        // Even if the holder wasn't officially recycleable, dispatch that it
1833                        // was recycled anyway in case there are resources to unbind.
1834                        dispatchViewRecycled(holder);
1835
1836                        // Drop out of the cache search and try something else instead,
1837                        // we won't find another match here.
1838                        break;
1839                    }
1840                    if (DEBUG) {
1841                        Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
1842                                ") found match in cache: " + holder);
1843                    }
1844                    return holder;
1845                }
1846            }
1847
1848            // Give up. Head to the shared pool.
1849            if (DEBUG) {
1850                Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
1851                    ") fetching from shared pool");
1852            }
1853            return getRecycledViewPool().getRecycledView(type);
1854        }
1855
1856        ViewHolder getScrapViewForId(long id, int type) {
1857            // Look in our attached views first
1858            final int count = mAttachedScrap.size();
1859            for (int i = 0; i < count; i++) {
1860                final ViewHolder holder = mAttachedScrap.get(i);
1861                if (holder.getItemId() == id) {
1862                    if (type == holder.getItemViewType()) {
1863                        mAttachedScrap.remove(i);
1864                        holder.setScrapContainer(null);
1865                        return holder;
1866                    } else {
1867                        break;
1868                    }
1869                }
1870            }
1871
1872            // Search the first-level cache
1873            final int cacheSize = mCachedViews.size();
1874            for (int i = 0; i < cacheSize; i++) {
1875                final ViewHolder holder = mCachedViews.get(i);
1876                if (holder.getItemId() == id) {
1877                    mCachedViews.remove(i);
1878                    return holder;
1879                }
1880            }
1881
1882            // That didn't work, look for an unordered view of the right type instead.
1883            // The holder's position won't match so the calling code will need to have
1884            // the adapter rebind it.
1885            return getRecycledViewPool().getRecycledView(type);
1886        }
1887
1888        void dispatchViewRecycled(ViewHolder holder) {
1889            if (mRecyclerListener != null) {
1890                mRecyclerListener.onViewRecycled(holder);
1891            }
1892            if (mAdapter != null) {
1893                mAdapter.onViewRecycled(holder);
1894            }
1895            if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
1896        }
1897
1898        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
1899            clear();
1900            getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter);
1901        }
1902
1903        void offsetPositionRecordsForInsert(int insertedAt, int count) {
1904            final int cachedCount = mCachedViews.size();
1905            for (int i = 0; i < cachedCount; i++) {
1906                final ViewHolder holder = mCachedViews.get(i);
1907                if (holder != null && holder.getPosition() >= insertedAt) {
1908                    if (DEBUG) {
1909                        Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
1910                                holder + " now at position " + (holder.mPosition + count));
1911                    }
1912                    holder.mPosition += count;
1913                }
1914            }
1915        }
1916
1917        void offsetPositionRecordsForRemove(int removedFrom, int count) {
1918            final int removedEnd = removedFrom + count;
1919            final int cachedCount = mCachedViews.size();
1920            for (int i = cachedCount - 1; i >= 0; i--) {
1921                final ViewHolder holder = mCachedViews.get(i);
1922                if (holder != null) {
1923                    if (holder.getPosition() >= removedEnd) {
1924                        if (DEBUG) {
1925                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
1926                                    " holder " + holder + " now at position " +
1927                                    (holder.mPosition - count));
1928                        }
1929                        holder.mPosition -= count;
1930                    } else if (holder.getPosition() >= removedFrom) {
1931                        // Item for this view was removed. Dump it from the cache.
1932                        if (DEBUG) {
1933                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
1934                                    " holder " + holder + " now placed in pool");
1935                        }
1936                        mCachedViews.remove(i);
1937                        getRecycledViewPool().putRecycledView(holder);
1938                        dispatchViewRecycled(holder);
1939                    }
1940                }
1941            }
1942        }
1943
1944        void setRecycledViewPool(RecycledViewPool pool) {
1945            if (mRecyclerPool != null) {
1946                mRecyclerPool.detach();
1947            }
1948            mRecyclerPool = pool;
1949            if (pool != null) {
1950                mRecyclerPool.attach(getAdapter());
1951            }
1952        }
1953
1954        RecycledViewPool getRecycledViewPool() {
1955            if (mRecyclerPool == null) {
1956                mRecyclerPool = new RecycledViewPool();
1957            }
1958            return mRecyclerPool;
1959        }
1960
1961        ViewHolder findViewHolderForPosition(int position) {
1962            final int cachedCount = mCachedViews.size();
1963            for (int i = 0; i < cachedCount; i++) {
1964                final ViewHolder holder = mCachedViews.get(i);
1965                if (holder != null && holder.getPosition() == position) {
1966                    mCachedViews.remove(i);
1967                    return holder;
1968                }
1969            }
1970            return null;
1971        }
1972
1973        ViewHolder findViewHolderForItemId(long id) {
1974            if (!mAdapter.hasStableIds()) {
1975                return null;
1976            }
1977
1978            final int cachedCount = mCachedViews.size();
1979            for (int i = 0; i < cachedCount; i++) {
1980                final ViewHolder holder = mCachedViews.get(i);
1981                if (holder != null && holder.getItemId() == id) {
1982                    mCachedViews.remove(i);
1983                    return holder;
1984                }
1985            }
1986            return null;
1987        }
1988
1989        void viewRangeUpdate(int positionStart, int itemCount) {
1990            final int positionEnd = positionStart + itemCount;
1991            final int cachedCount = mCachedViews.size();
1992            for (int i = 0; i < cachedCount; i++) {
1993                final ViewHolder holder = mCachedViews.get(i);
1994                if (holder == null) {
1995                    continue;
1996                }
1997
1998                final int pos = holder.getPosition();
1999                if (pos >= positionStart && pos < positionEnd) {
2000                    holder.addFlags(ViewHolder.FLAG_UPDATE);
2001                }
2002            }
2003        }
2004
2005        void markKnownViewsInvalid() {
2006            final int cachedCount = mCachedViews.size();
2007            for (int i = 0; i < cachedCount; i++) {
2008                final ViewHolder holder = mCachedViews.get(i);
2009                if (holder != null) {
2010                    holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
2011                }
2012            }
2013        }
2014    }
2015
2016    /**
2017     * Base class for an Adapter
2018     *
2019     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
2020     * within a {@link RecyclerView}.</p>
2021     */
2022    public static abstract class Adapter<VH extends ViewHolder> {
2023        private final AdapterDataObservable mObservable = new AdapterDataObservable();
2024        private boolean mHasStableIds = false;
2025
2026        /** @deprecated */
2027        private int mViewTypeCount = 1;
2028
2029        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
2030        public abstract void onBindViewHolder(VH holder, int position);
2031
2032        public final VH createViewHolder(ViewGroup parent, int viewType) {
2033            final VH holder = onCreateViewHolder(parent, viewType);
2034            holder.mItemViewType = viewType;
2035            return holder;
2036        }
2037
2038        public final void bindViewHolder(VH holder, int position) {
2039            holder.mPosition = position;
2040            if (hasStableIds()) {
2041                holder.mItemId = getItemId(position);
2042            }
2043            onBindViewHolder(holder, position);
2044            holder.setFlags(ViewHolder.FLAG_BOUND,
2045                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
2046        }
2047
2048        /**
2049         * Return the view type of the item at <code>position</code> for the purposes
2050         * of view recycling.
2051         *
2052         * <p>The default implementation of this method returns 0, making the assumption of
2053         * a single view type for the adapter. Unlike ListView adapters, types need not
2054         * be contiguous. Consider using id resources to uniquely identify item view types.
2055         *
2056         * @param position position to query
2057         * @return integer value identifying the type of the view needed to represent the item at
2058         *                 <code>position</code>. Type codes need not be contiguous.
2059         */
2060        public int getItemViewType(int position) {
2061            return 0;
2062        }
2063
2064        /**
2065         * Set the number of item view types required by this adapter to display its data set.
2066         * This may not be changed while the adapter has observers - e.g. while the adapter
2067         * is set on a {#link RecyclerView}.
2068         *
2069         * @param count Number of item view types required
2070         * @see #getItemViewTypeCount()
2071         * @see #getItemViewType(int)
2072         *
2073         * @deprecated This method is no longer necessary. View types are now unbounded.
2074         */
2075        public void setItemViewTypeCount(int count) {
2076            Log.w(TAG, "setItemViewTypeCount is deprecated and no longer needed.");
2077            mViewTypeCount = count;
2078        }
2079
2080        /**
2081         * Retrieve the number of item view types required by this adapter to display its data set.
2082         *
2083         * @return Number of item view types supported
2084         * @see #setItemViewTypeCount(int)
2085         * @see #getItemViewType(int)
2086         *
2087         * @deprecated This method is no longer necessary. View types are now unbounded.
2088         */
2089        public final int getItemViewTypeCount() {
2090            Log.w(TAG, "getItemViewTypeCount is no longer needed. " +
2091                    "View type count is now unbounded.");
2092            return mViewTypeCount;
2093        }
2094
2095        public void setHasStableIds(boolean hasStableIds) {
2096            if (hasObservers()) {
2097                throw new IllegalStateException("Cannot change whether this adapter has " +
2098                        "stable IDs while the adapter has registered observers.");
2099            }
2100            mHasStableIds = hasStableIds;
2101        }
2102
2103        /**
2104         * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
2105         * would return false this method should return {@link #NO_ID}. The default implementation
2106         * of this method returns {@link #NO_ID}.
2107         *
2108         * @param position Adapter position to query
2109         * @return the stable ID of the item at position
2110         */
2111        public long getItemId(int position) {
2112            return NO_ID;
2113        }
2114
2115        public abstract int getItemCount();
2116
2117        /**
2118         * Returns true if this adapter publishes a unique <code>long</code> value that can
2119         * act as a key for the item at a given position in the data set. If that item is relocated
2120         * in the data set, the ID returned for that item should be the same.
2121         *
2122         * @return true if this adapter's items have stable IDs
2123         */
2124        public final boolean hasStableIds() {
2125            return mHasStableIds;
2126        }
2127
2128        /**
2129         * Called when a view created by this adapter has been recycled.
2130         *
2131         * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
2132         * needs to be attached to its parent {@link RecyclerView}. This can be because it has
2133         * fallen out of visibility or a set of cached views represented by views still
2134         * attached to the parent RecyclerView. If an item view has large or expensive data
2135         * bound to it such as large bitmaps, this may be a good place to release those
2136         * resources.</p>
2137         *
2138         * @param holder The ViewHolder for the view being recycled
2139         */
2140        public void onViewRecycled(VH holder) {
2141        }
2142
2143        /**
2144         * Returns true if one or more observers are attached to this adapter.
2145         * @return true if this adapter has observers
2146         */
2147        public final boolean hasObservers() {
2148            return mObservable.hasObservers();
2149        }
2150
2151        /**
2152         * Register a new observer to listen for data changes.
2153         *
2154         * <p>The adapter may publish a variety of events describing specific changes.
2155         * Not all adapters may support all change types and some may fall back to a generic
2156         * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged()
2157         * "something changed"} event if more specific data is not available.</p>
2158         *
2159         * <p>Components registering observers with an adapter are responsible for
2160         * {@link #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
2161         * unregistering} those observers when finished.</p>
2162         *
2163         * @param observer Observer to register
2164         *
2165         * @see #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
2166         */
2167        public void registerAdapterDataObserver(AdapterDataObserver observer) {
2168            mObservable.registerObserver(observer);
2169        }
2170
2171        /**
2172         * Unregister an observer currently listening for data changes.
2173         *
2174         * <p>The unregistered observer will no longer receive events about changes
2175         * to the adapter.</p>
2176         *
2177         * @param observer Observer to unregister
2178         *
2179         * @see #registerAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
2180         */
2181        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
2182            mObservable.unregisterObserver(observer);
2183        }
2184
2185        /**
2186         * Notify any registered observers that the data set has changed.
2187         *
2188         * <p>There are two different classes of data change events, item changes and structural
2189         * changes. Item changes are when a single item has its data updated but no positional
2190         * changes have occurred. Structural changes are when items are inserted, deleted or moved
2191         * within the data set.</p>
2192         *
2193         * <p>This event does not specify what about the data set has changed, forcing
2194         * any observers to assume that all existing items and structure may no longer be valid.
2195         * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
2196         *
2197         * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
2198         * for adapters that report that they have {@link #hasStableIds() stable IDs} when
2199         * this method is used. This can help for the purposes of animation and visual
2200         * object persistence but individual item views will still need to be rebound
2201         * and relaid out.</p>
2202         *
2203         * <p>If you are writing an adapter it will always be more efficient to use the more
2204         * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
2205         * as a last resort.</p>
2206         *
2207         * @see #notifyItemChanged(int)
2208         * @see #notifyItemInserted(int)
2209         * @see #notifyItemRemoved(int)
2210         * @see #notifyItemRangeChanged(int, int)
2211         * @see #notifyItemRangeInserted(int, int)
2212         * @see #notifyItemRangeRemoved(int, int)
2213         */
2214        public final void notifyDataSetChanged() {
2215            mObservable.notifyChanged();
2216        }
2217
2218        /**
2219         * Notify any registered observers that the item at <code>position</code> has changed.
2220         *
2221         * <p>This is an item change event, not a structural change event. It indicates that any
2222         * reflection of the data at <code>position</code> is out of date and should be updated.
2223         * The item at <code>position</code> retains the same identity.</p>
2224         *
2225         * @param position Position of the item that has changed
2226         *
2227         * @see #notifyItemRangeChanged(int, int)
2228         */
2229        public final void notifyItemChanged(int position) {
2230            mObservable.notifyItemRangeChanged(position, 1);
2231        }
2232
2233        /**
2234         * Notify any registered observers that the <code>itemCount</code> items starting at
2235         * position <code>positionStart</code> have changed.
2236         *
2237         * <p>This is an item change event, not a structural change event. It indicates that
2238         * any reflection of the data in the given position range is out of date and should
2239         * be updated. The items in the given range retain the same identity.</p>
2240         *
2241         * @param positionStart Position of the first item that has changed
2242         * @param itemCount Number of items that have changed
2243         *
2244         * @see #notifyItemChanged(int)
2245         */
2246        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
2247            mObservable.notifyItemRangeChanged(positionStart, itemCount);
2248        }
2249
2250        /**
2251         * Notify any registered observers that the item reflected at <code>position</code>
2252         * has been newly inserted. The item previously at <code>position</code> is now at
2253         * position <code>position + 1</code>.
2254         *
2255         * <p>This is a structural change event. Representations of other existing items in the
2256         * data set are still considered up to date and will not be rebound, though their
2257         * positions may be altered.</p>
2258         *
2259         * @param position Position of the newly inserted item in the data set
2260         *
2261         * @see #notifyItemRangeInserted(int, int)
2262         */
2263        public final void notifyItemInserted(int position) {
2264            mObservable.notifyItemRangeInserted(position, 1);
2265        }
2266
2267        /**
2268         * Notify any registered observers that the currently reflected <code>itemCount</code>
2269         * items starting at <code>positionStart</code> have been newly inserted. The items
2270         * previously located at <code>positionStart</code> and beyond can now be found starting
2271         * at position <code>positionStart + itemCount</code>.
2272         *
2273         * <p>This is a structural change event. Representations of other existing items in the
2274         * data set are still considered up to date and will not be rebound, though their positions
2275         * may be altered.</p>
2276         *
2277         * @param positionStart Position of the first item that was inserted
2278         * @param itemCount Number of items inserted
2279         *
2280         * @see #notifyItemInserted(int)
2281         */
2282        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
2283            mObservable.notifyItemRangeInserted(positionStart, itemCount);
2284        }
2285
2286        /**
2287         * Notify any registered observers that the item previously located at <code>position</code>
2288         * has been removed from the data set. The items previously located at and after
2289         * <code>position</code> may now be found at <code>oldPosition - 1</code>.
2290         *
2291         * <p>This is a structural change event. Representations of other existing items in the
2292         * data set are still considered up to date and will not be rebound, though their positions
2293         * may be altered.</p>
2294         *
2295         * @param position Position of the item that has now been removed
2296         *
2297         * @see #notifyItemRangeRemoved(int, int)
2298         */
2299        public final void notifyItemRemoved(int position) {
2300            mObservable.notifyItemRangeRemoved(position, 1);
2301        }
2302
2303        /**
2304         * Notify any registered observers that the <code>itemCount</code> items previously
2305         * located at <code>positionStart</code> have been removed from the data set. The items
2306         * previously located at and after <code>positionStart + itemCount</code> may now be found
2307         * at <code>oldPosition - itemCount</code>.
2308         *
2309         * <p>This is a structural change event. Representations of other existing items in the data
2310         * set are still considered up to date and will not be rebound, though their positions
2311         * may be altered.</p>
2312         *
2313         * @param positionStart Previous position of the first item that was removed
2314         * @param itemCount Number of items removed from the data set
2315         */
2316        public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
2317            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
2318        }
2319    }
2320
2321    /**
2322     * A <code>LayoutManager</code> is responsible for measuring and positioning item views
2323     * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
2324     * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
2325     * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
2326     * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
2327     * layout managers are provided for general use.
2328     */
2329    public static abstract class LayoutManager {
2330        RecyclerView mRecyclerView;
2331
2332        /**
2333         * @deprecated LayoutManagers should not access the RecyclerView they are bound to directly.
2334         *             See the other methods on LayoutManager for accessing child views and
2335         *             container properties instead. <em>This method will be removed.</em>
2336         */
2337        public final RecyclerView getRecyclerView() {
2338            return mRecyclerView;
2339        }
2340
2341        /**
2342         * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
2343         * is attached to a window.
2344         *
2345         * <p>Subclass implementations should always call through to the superclass implementation.
2346         * </p>
2347         *
2348         * @param view The RecyclerView this LayoutManager is bound to
2349         */
2350        public void onAttachedToWindow(RecyclerView view) {
2351        }
2352
2353        /**
2354         * Called when this LayoutManager is detached from its parent RecyclerView or when
2355         * its parent RecyclerView is detached from its window.
2356         *
2357         * <p>Subclass implementations should always call through to the superclass implementation.
2358         * </p>
2359         *
2360         * @param view The RecyclerView this LayoutManager is bound to
2361         */
2362        public void onDetachedFromWindow(RecyclerView view) {
2363        }
2364
2365        /**
2366         * @deprecated Use
2367         * {@link #layoutChildren(RecyclerView.Adapter, RecyclerView.Recycler, boolean)}
2368         */
2369        public void layoutChildren(Adapter adapter, Recycler recycler) {
2370        }
2371
2372        /**
2373         * Lay out all relevant child views from the given adapter.
2374         *
2375         * @param adapter Adapter that will supply and bind views from a data set
2376         * @param recycler Recycler to use for fetching potentially cached views for a position
2377         * @param structureChanged true if the structure of the data set has changed since
2378         *                         the last call to layoutChildren, false otherwise
2379         */
2380        public void layoutChildren(Adapter adapter, Recycler recycler, boolean structureChanged) {
2381            layoutChildren(adapter, recycler);
2382        }
2383
2384        /**
2385         * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
2386         *
2387         * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
2388         * to store extra information specific to the layout. Client code should subclass
2389         * {@link RecyclerView.LayoutParams} for this purpose.</p>
2390         *
2391         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
2392         * you must also override
2393         * {@link #checkLayoutParams(LayoutParams)},
2394         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
2395         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
2396         *
2397         * @return A new LayoutParams for a child view
2398         */
2399        public abstract LayoutParams generateDefaultLayoutParams();
2400
2401        /**
2402         * Determines the validity of the supplied LayoutParams object.
2403         *
2404         * <p>This should check to make sure that the object is of the correct type
2405         * and all values are within acceptable ranges. The default implementation
2406         * returns <code>true</code> for non-null params.</p>
2407         *
2408         * @param lp LayoutParams object to check
2409         * @return true if this LayoutParams object is valid, false otherwise
2410         */
2411        public boolean checkLayoutParams(LayoutParams lp) {
2412            return lp != null;
2413        }
2414
2415        /**
2416         * Create a LayoutParams object suitable for this LayoutManager, copying relevant
2417         * values from the supplied LayoutParams object if possible.
2418         *
2419         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
2420         * you must also override
2421         * {@link #checkLayoutParams(LayoutParams)},
2422         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
2423         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
2424         *
2425         * @param lp Source LayoutParams object to copy values from
2426         * @return a new LayoutParams object
2427         */
2428        public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
2429            if (lp instanceof LayoutParams) {
2430                return new LayoutParams((LayoutParams) lp);
2431            } else if (lp instanceof MarginLayoutParams) {
2432                return new LayoutParams((MarginLayoutParams) lp);
2433            } else {
2434                return new LayoutParams(lp);
2435            }
2436        }
2437
2438        /**
2439         * Create a LayoutParams object suitable for this LayoutManager from
2440         * an inflated layout resource.
2441         *
2442         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
2443         * you must also override
2444         * {@link #checkLayoutParams(LayoutParams)},
2445         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
2446         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
2447         *
2448         * @param c Context for obtaining styled attributes
2449         * @param attrs AttributeSet describing the supplied arguments
2450         * @return a new LayoutParams object
2451         */
2452        public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
2453            return new LayoutParams(c, attrs);
2454        }
2455
2456        /**
2457         * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
2458         * The default implementation does nothing and returns 0.
2459         *
2460         * @param dx distance to scroll by in pixels. X increases as scroll position
2461         *           approaches the right.
2462         * @return The actual distance scrolled. The return value will be negative if dx was
2463         *         negative and scrolling proceeeded in that direction.
2464         *         <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
2465         */
2466        public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler) {
2467            return scrollHorizontallyBy(dx, recycler);
2468        }
2469
2470        /**
2471         * @deprecated API changed to include the Adapter to use. Override
2472         * {@link #scrollHorizontallyBy(int, android.support.v7.widget.RecyclerView.Adapter,
2473         * android.support.v7.widget.RecyclerView.Recycler)} instead.
2474         */
2475        public int scrollHorizontallyBy(int dx, Recycler recycler) {
2476            return 0;
2477        }
2478
2479        /**
2480         * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
2481         * The default implementation does nothing and returns 0.
2482         *
2483         * @param dy distance to scroll in pixels. Y increases as scroll position
2484         *           approaches the bottom.
2485         * @return The actual distance scrolled. The return value will be negative if dy was
2486         *         negative and scrolling proceeeded in that direction.
2487         *         <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
2488         */
2489        public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler) {
2490            return scrollVerticallyBy(dy, recycler);
2491        }
2492
2493        /**
2494         * @deprecated API changed to include the Adapter to use. Override
2495         * {@link #scrollVerticallyBy(int, android.support.v7.widget.RecyclerView.Adapter,
2496         * android.support.v7.widget.RecyclerView.Recycler)} instead.
2497         */
2498        public int scrollVerticallyBy(int dy, Recycler recycler) {
2499            return 0;
2500        }
2501
2502        /**
2503         * Query if horizontal scrolling is currently supported. The default implementation
2504         * returns false.
2505         *
2506         * @return True if this LayoutManager can scroll the current contents horizontally
2507         */
2508        public boolean canScrollHorizontally() {
2509            return false;
2510        }
2511
2512        /**
2513         * Query if vertical scrolling is currently supported. The default implementation
2514         * returns false.
2515         *
2516         * @return True if this LayoutManager can scroll the current contents vertically
2517         */
2518        public boolean canScrollVertically() {
2519            return false;
2520        }
2521
2522        /**
2523         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
2524         * use this method to add views obtained from a {@link Recycler} using
2525         * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}.
2526         *
2527         * @param child View to add
2528         * @param index Index to add child at
2529         */
2530        public void addView(View child, int index) {
2531            final ViewHolder holder = mRecyclerView.getChildViewHolder(child);
2532            if (holder.isScrap()) {
2533                holder.unScrap();
2534                mRecyclerView.attachViewToParent(child, index, child.getLayoutParams());
2535                if (DISPATCH_TEMP_DETACH) {
2536                    ViewCompat.dispatchFinishTemporaryDetach(child);
2537                }
2538            } else {
2539                mRecyclerView.addView(child, index);
2540            }
2541        }
2542
2543        /**
2544         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
2545         * use this method to add views obtained from a {@link Recycler} using
2546         * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}.
2547         *
2548         * @param child View to add
2549         */
2550        public void addView(View child) {
2551            addView(child, -1);
2552        }
2553
2554        /**
2555         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
2556         * use this method to completely remove a child view that is no longer needed.
2557         * LayoutManagers should strongly consider recycling removed views using
2558         * {@link Recycler#recycleView(android.view.View)}.
2559         *
2560         * @param child View to remove
2561         */
2562        public void removeView(View child) {
2563            mRecyclerView.removeView(child);
2564        }
2565
2566        /**
2567         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
2568         * use this method to completely remove a child view that is no longer needed.
2569         * LayoutManagers should strongly consider recycling removed views using
2570         * {@link Recycler#recycleView(android.view.View)}.
2571         *
2572         * @param index Index of the child view to remove
2573         */
2574        public void removeViewAt(int index) {
2575            mRecyclerView.removeViewAt(index);
2576        }
2577
2578        /**
2579         * Remove all views from the currently attached RecyclerView. This will not recycle
2580         * any of the affected views; the LayoutManager is responsible for doing so if desired.
2581         */
2582        public void removeAllViews() {
2583            mRecyclerView.removeAllViews();
2584        }
2585
2586        /**
2587         * Temporarily detach a child view.
2588         *
2589         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
2590         * views currently attached to the RecyclerView. Generally LayoutManager implementations
2591         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
2592         * so that the detached view may be rebound and reused.</p>
2593         *
2594         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
2595         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
2596         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
2597         * before the LayoutManager entry point method called by RecyclerView returns.</p>
2598         *
2599         * @param child Child to detach
2600         */
2601        public void detachView(View child) {
2602            if (DISPATCH_TEMP_DETACH) {
2603                ViewCompat.dispatchStartTemporaryDetach(child);
2604            }
2605            mRecyclerView.detachViewFromParent(child);
2606        }
2607
2608        /**
2609         * Temporarily detach a child view.
2610         *
2611         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
2612         * views currently attached to the RecyclerView. Generally LayoutManager implementations
2613         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
2614         * so that the detached view may be rebound and reused.</p>
2615         *
2616         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
2617         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
2618         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
2619         * before the LayoutManager entry point method called by RecyclerView returns.</p>
2620         *
2621         * @param index Index of the child to detach
2622         */
2623        public void detachViewAt(int index) {
2624            if (DISPATCH_TEMP_DETACH) {
2625                ViewCompat.dispatchStartTemporaryDetach(mRecyclerView.getChildAt(index));
2626            }
2627            mRecyclerView.detachViewFromParent(index);
2628        }
2629
2630        /**
2631         * Reattach a previously {@link #detachView(android.view.View) detached} view.
2632         * This method should not be used to reattach views that were previously
2633         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
2634         *
2635         * @param child Child to reattach
2636         * @param index Intended child index for child
2637         * @param lp LayoutParams for child
2638         */
2639        public void attachView(View child, int index, LayoutParams lp) {
2640            mRecyclerView.attachViewToParent(child, index, lp);
2641            if (DISPATCH_TEMP_DETACH)  {
2642                ViewCompat.dispatchFinishTemporaryDetach(child);
2643            }
2644        }
2645
2646        /**
2647         * Reattach a previously {@link #detachView(android.view.View) detached} view.
2648         * This method should not be used to reattach views that were previously
2649         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
2650         *
2651         * @param child Child to reattach
2652         * @param index Intended child index for child
2653         */
2654        public void attachView(View child, int index) {
2655            attachView(child, index, (LayoutParams) child.getLayoutParams());
2656        }
2657
2658        /**
2659         * Reattach a previously {@link #detachView(android.view.View) detached} view.
2660         * This method should not be used to reattach views that were previously
2661         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
2662         *
2663         * @param child Child to reattach
2664         */
2665        public void attachView(View child) {
2666            attachView(child, -1);
2667        }
2668
2669        /**
2670         * Finish removing a view that was previously temporarily
2671         * {@link #detachView(android.view.View) detached}.
2672         *
2673         * @param child Detached child to remove
2674         */
2675        public void removeDetachedView(View child) {
2676            mRecyclerView.removeDetachedView(child, false);
2677        }
2678
2679        /**
2680         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
2681         *
2682         * <p>Scrapping a view allows it to be rebound and reused to show updated or
2683         * different data.</p>
2684         *
2685         * @param child Child to detach and scrap
2686         * @param recycler Recycler to deposit the new scrap view into
2687         */
2688        public void detachAndScrapView(View child, Recycler recycler) {
2689            detachView(child);
2690            recycler.scrapView(child);
2691        }
2692
2693        /**
2694         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
2695         *
2696         * <p>Scrapping a view allows it to be rebound and reused to show updated or
2697         * different data.</p>
2698         *
2699         * @param index Index of child to detach and scrap
2700         * @param recycler Recycler to deposit the new scrap view into
2701         */
2702        public void detachAndScrapViewAt(int index, Recycler recycler) {
2703            final View child = getChildAt(index);
2704            detachViewAt(index);
2705            recycler.scrapView(child);
2706        }
2707
2708        /**
2709         * Remove a child view and recycle it using the given Recycler.
2710         *
2711         * @param child Child to remove and recycle
2712         * @param recycler Recycler to use to recycle child
2713         */
2714        public void removeAndRecycleView(View child, Recycler recycler) {
2715            removeView(child);
2716            recycler.recycleView(child);
2717        }
2718
2719        /**
2720         * Remove a child view and recycle it using the given Recycler.
2721         *
2722         * @param index Index of child to remove and recycle
2723         * @param recycler Recycler to use to recycle child
2724         */
2725        public void removeAndRecycleViewAt(int index, Recycler recycler) {
2726            final View view = getChildAt(index);
2727            removeViewAt(index);
2728            recycler.recycleView(view);
2729        }
2730
2731        /**
2732         * Return the current number of child views attached to the parent RecyclerView.
2733         * This does not include child views that were temporarily detached and/or scrapped.
2734         *
2735         * @return Number of attached children
2736         */
2737        public int getChildCount() {
2738            return mRecyclerView != null ? mRecyclerView.getChildCount() : 0;
2739        }
2740
2741        /**
2742         * Return the child view at the given index
2743         * @param index Index of child to return
2744         * @return Child view at index
2745         */
2746        public View getChildAt(int index) {
2747            return mRecyclerView != null ? mRecyclerView.getChildAt(index) : null;
2748        }
2749
2750        /**
2751         * Return the width of the parent RecyclerView
2752         *
2753         * @return Width in pixels
2754         */
2755        public int getWidth() {
2756            return mRecyclerView != null ? mRecyclerView.getWidth() : 0;
2757        }
2758
2759        /**
2760         * Return the height of the parent RecyclerView
2761         *
2762         * @return Height in pixels
2763         */
2764        public int getHeight() {
2765            return mRecyclerView != null ? mRecyclerView.getHeight() : 0;
2766        }
2767
2768        /**
2769         * Return the left padding of the parent RecyclerView
2770         *
2771         * @return Padding in pixels
2772         */
2773        public int getPaddingLeft() {
2774            return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
2775        }
2776
2777        /**
2778         * Return the top padding of the parent RecyclerView
2779         *
2780         * @return Padding in pixels
2781         */
2782        public int getPaddingTop() {
2783            return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
2784        }
2785
2786        /**
2787         * Return the right padding of the parent RecyclerView
2788         *
2789         * @return Padding in pixels
2790         */
2791        public int getPaddingRight() {
2792            return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
2793        }
2794
2795        /**
2796         * Return the bottom padding of the parent RecyclerView
2797         *
2798         * @return Padding in pixels
2799         */
2800        public int getPaddingBottom() {
2801            return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
2802        }
2803
2804        /**
2805         * Return the start padding of the parent RecyclerView
2806         *
2807         * @return Padding in pixels
2808         */
2809        public int getPaddingStart() {
2810            return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0;
2811        }
2812
2813        /**
2814         * Return the end padding of the parent RecyclerView
2815         *
2816         * @return Padding in pixels
2817         */
2818        public int getPaddingEnd() {
2819            return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0;
2820        }
2821
2822        /**
2823         * Returns true if the RecyclerView this LayoutManager is bound to has focus.
2824         *
2825         * @return True if the RecyclerView has focus, false otherwise.
2826         * @see View#isFocused()
2827         */
2828        public boolean isFocused() {
2829            return mRecyclerView != null && mRecyclerView.isFocused();
2830        }
2831
2832        /**
2833         * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
2834         *
2835         * @return true if the RecyclerView has or contains focus
2836         * @see View#hasFocus()
2837         */
2838        public boolean hasFocus() {
2839            return mRecyclerView != null && mRecyclerView.hasFocus();
2840        }
2841
2842        /**
2843         * Return the number of items in the adapter bound to the parent RecyclerView
2844         *
2845         * @return Items in the bound adapter
2846         */
2847        public int getItemCount() {
2848            final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
2849            return a != null ? a.getItemCount() : 0;
2850        }
2851
2852        /**
2853         * Offset all child views attached to the parent RecyclerView by dx pixels along
2854         * the horizontal axis.
2855         *
2856         * @param dx Pixels to offset by
2857         */
2858        public void offsetChildrenHorizontal(int dx) {
2859            if (mRecyclerView != null) {
2860                mRecyclerView.offsetChildrenHorizontal(dx);
2861            }
2862        }
2863
2864        /**
2865         * Offset all child views attached to the parent RecyclerView by dy pixels along
2866         * the vertical axis.
2867         *
2868         * @param dy Pixels to offset by
2869         */
2870        public void offsetChildrenVertical(int dy) {
2871            if (mRecyclerView != null) {
2872                mRecyclerView.offsetChildrenVertical(dy);
2873            }
2874        }
2875
2876        /**
2877         * Temporarily detach and scrap all currently attached child views. Views will be scrapped
2878         * into the given Recycler. The Recycler may prefer to reuse scrap views before
2879         * other views that were previously recycled.
2880         *
2881         * @param recycler Recycler to scrap views into
2882         */
2883        public void detachAndScrapAttachedViews(Recycler recycler) {
2884            final int childCount = getChildCount();
2885            for (int i = childCount - 1; i >= 0; i--) {
2886                final View v = getChildAt(i);
2887                detachViewAt(i);
2888                recycler.scrapView(v);
2889            }
2890        }
2891
2892        /**
2893         * Remove and recycle all scrap views currently tracked by Recycler. Recycled views
2894         * will be made available for later reuse.
2895         *
2896         * @param recycler Recycler tracking scrap views to remove
2897         */
2898        public void removeAndRecycleScrap(Recycler recycler) {
2899            final int scrapCount = recycler.getScrapCount();
2900            for (int i = 0; i < scrapCount; i++) {
2901                final View scrap = recycler.getScrapViewAt(i);
2902                mRecyclerView.removeDetachedView(scrap, false);
2903                recycler.quickRecycleScrapView(scrap);
2904            }
2905            recycler.clearScrap();
2906        }
2907
2908        /**
2909         * Measure a child view using standard measurement policy, taking the padding
2910         * of the parent RecyclerView into account.
2911         *
2912         * <p>If the RecyclerView can be scrolled in either dimension the caller may
2913         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
2914         *
2915         * @param child Child view to measure
2916         * @param widthUsed Width in pixels currently consumed by other views, if relevant
2917         * @param heightUsed Height in pixels currently consumed by other views, if relevant
2918         */
2919        public void measureChild(View child, int widthUsed, int heightUsed) {
2920            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2921
2922            final int widthSpec = getChildMeasureSpec(getWidth(),
2923                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
2924                    canScrollHorizontally());
2925            final int heightSpec = getChildMeasureSpec(getHeight(),
2926                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
2927                    canScrollVertically());
2928            child.measure(widthSpec, heightSpec);
2929        }
2930
2931        /**
2932         * Measure a child view using standard measurement policy, taking the padding
2933         * of the parent RecyclerView and the child margins into account.
2934         *
2935         * <p>If the RecyclerView can be scrolled in either dimension the caller may
2936         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
2937         *
2938         * @param child Child view to measure
2939         * @param widthUsed Width in pixels currently consumed by other views, if relevant
2940         * @param heightUsed Height in pixels currently consumed by other views, if relevant
2941         */
2942        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
2943            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
2944
2945            final int widthSpec = getChildMeasureSpec(getWidth(),
2946                    getPaddingLeft() + getPaddingRight() +
2947                            lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
2948                    canScrollHorizontally());
2949            final int heightSpec = getChildMeasureSpec(getHeight(),
2950                    getPaddingTop() + getPaddingBottom() +
2951                            lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
2952                    canScrollVertically());
2953            child.measure(widthSpec, heightSpec);
2954        }
2955
2956        /**
2957         * Calculate a MeasureSpec value for measuring a child view in one dimension.
2958         *
2959         * @param parentSize Size of the parent view where the child will be placed
2960         * @param padding Total space currently consumed by other elements of parent
2961         * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
2962         *                       Generally obtained from the child view's LayoutParams
2963         * @param canScroll true if the parent RecyclerView can scroll in this dimension
2964         *
2965         * @return a MeasureSpec value for the child view
2966         */
2967        public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
2968                boolean canScroll) {
2969            int size = Math.max(0, parentSize - padding);
2970            int resultSize = 0;
2971            int resultMode = 0;
2972
2973            if (canScroll) {
2974                if (childDimension >= 0) {
2975                    resultSize = childDimension;
2976                    resultMode = MeasureSpec.EXACTLY;
2977                } else {
2978                    // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
2979                    // instead using UNSPECIFIED.
2980                    resultSize = 0;
2981                    resultMode = MeasureSpec.UNSPECIFIED;
2982                }
2983            } else {
2984                if (childDimension >= 0) {
2985                    resultSize = childDimension;
2986                    resultMode = MeasureSpec.EXACTLY;
2987                } else if (childDimension == LayoutParams.FILL_PARENT) {
2988                    resultSize = size;
2989                    resultMode = MeasureSpec.EXACTLY;
2990                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
2991                    resultSize = size;
2992                    resultMode = MeasureSpec.AT_MOST;
2993                }
2994            }
2995            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
2996        }
2997
2998        /**
2999         * Called when searching for a focusable view in the given direction has failed
3000         * for the current content of the RecyclerView.
3001         *
3002         * <p>This is the LayoutManager's opportunity to populate views in the given direction
3003         * to fulfill the request if it can. The LayoutManager should attach and return
3004         * the view to be focused. The default implementation returns null.</p>
3005         *
3006         * @param focused The currently focused view
3007         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
3008         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
3009         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
3010         *                  or 0 for not applicable
3011         * @param recycler The recycler to use for obtaining views for currently offscreen items
3012         * @return The chosen view to be focused
3013         */
3014        public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
3015                Recycler recycler) {
3016            return onFocusSearchFailed(focused, direction, recycler);
3017        }
3018
3019        /**
3020         * @deprecated API changed to supply the Adapter. Override
3021         * {@link #onFocusSearchFailed(android.view.View, int,
3022         * android.support.v7.widget.RecyclerView.Adapter,
3023         * android.support.v7.widget.RecyclerView.Recycler)} instead.
3024         */
3025        public View onFocusSearchFailed(View focused, int direction, Recycler recycler) {
3026            return null;
3027        }
3028
3029        /**
3030         * @deprecated This method will be removed. Override {@link #requestChildRectangleOnScreen(
3031         * RecyclerView, android.view.View, android.graphics.Rect, boolean)} instead.
3032         */
3033        public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
3034            return requestChildRectangleOnScreen(mRecyclerView, child, rect, immediate);
3035        }
3036
3037        /**
3038         * Called when a child of the RecyclerView wants a particular rectangle to be positioned
3039         * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
3040         * android.graphics.Rect, boolean)} for more details.
3041         *
3042         * <p>The base implementation will attempt to perform a standard programmatic scroll
3043         * to bring the given rect into view, within the padded area of the RecyclerView.</p>
3044         *
3045         * @param child The direct child making the request.
3046         * @param rect  The rectangle in the child's coordinates the child
3047         *              wishes to be on the screen.
3048         * @param immediate True to forbid animated or delayed scrolling,
3049         *                  false otherwise
3050         * @return Whether the group scrolled to handle the operation
3051         */
3052        public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
3053                boolean immediate) {
3054            final int parentLeft = getPaddingLeft();
3055            final int parentTop = getPaddingTop();
3056            final int parentRight = getWidth() - getPaddingRight();
3057            final int parentBottom = getHeight() - getPaddingBottom();
3058            final int childLeft = child.getLeft() + rect.left;
3059            final int childTop = child.getTop() + rect.top;
3060            final int childRight = childLeft + rect.right;
3061            final int childBottom = childTop + rect.bottom;
3062
3063            final int offScreenLeft = Math.min(0, childLeft - parentLeft);
3064            final int offScreenTop = Math.min(0, childTop - parentTop);
3065            final int offScreenRight = Math.max(0, childRight - parentRight);
3066            final int offScreenBottom = Math.max(0, childBottom - parentBottom);
3067
3068            // Favor the "start" layout direction over the end when bringing one side or the other
3069            // of a large rect into view.
3070            final int dx;
3071            if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
3072                dx = offScreenRight != 0 ? offScreenRight : offScreenLeft;
3073            } else {
3074                dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight;
3075            }
3076
3077            // Favor bringing the top into view over the bottom
3078            final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;
3079
3080            if (dx != 0 || dy != 0) {
3081                if (immediate) {
3082                    parent.scrollBy(dx, dy);
3083                } else {
3084                    parent.smoothScrollBy(dx, dy);
3085                }
3086                return true;
3087            }
3088            return false;
3089        }
3090
3091        /**
3092         * Called when a descendant view of the RecyclerView requests focus.
3093         *
3094         * <p>A LayoutManager wishing to keep focused views aligned in a specific
3095         * portion of the view may implement that behavior in an override of this method.</p>
3096         *
3097         * <p>If the LayoutManager executes different behavior that should override the default
3098         * behavior of scrolling the focused child on screen instead of running alongside it,
3099         * this method should return true.</p>
3100         *
3101         * @param parent The RecyclerView hosting this LayoutManager
3102         * @param child Direct child of the RecyclerView containing the newly focused view
3103         * @param focused The newly focused view. This may be the same view as child
3104         * @return true if the default scroll behavior should be suppressed
3105         */
3106        public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
3107            return onRequestChildFocus(child, focused);
3108        }
3109
3110        /**
3111         * @deprecated This method will be removed. Override
3112         * {@link #onRequestChildFocus(RecyclerView, android.view.View, android.view.View)}
3113         * instead.
3114         */
3115        public boolean onRequestChildFocus(View child, View focused) {
3116            return false;
3117        }
3118
3119        /**
3120         * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
3121         * The LayoutManager may use this opportunity to clear caches and configure state such
3122         * that it can relayout appropriately with the new data and potentially new view types.
3123         *
3124         * <p>The default implementation removes all currently attached views.</p>
3125         */
3126        public void onAdapterChanged() {
3127            removeAllViews();
3128        }
3129    }
3130
3131    /**
3132     * An ItemDecoration allows the application to add a special drawing and layout offset
3133     * to specific item views from the adapter's data set. This can be useful for drawing dividers
3134     * between items, highlights, visual grouping boundaries and more.
3135     *
3136     * <p>All ItemDecorations are drawn in the order they were added, before the item
3137     * views (in {@link ItemDecoration#onDraw(Canvas) onDraw()} and after the items
3138     * (in {@link ItemDecoration#onDrawOver(Canvas)}.</p>
3139     */
3140    public static abstract class ItemDecoration {
3141        /**
3142         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
3143         * Any content drawn by this method will be drawn before the item views are drawn,
3144         * and will thus appear underneath the views.
3145         *
3146         * @param c Canvas to draw into
3147         */
3148        public void onDraw(Canvas c) {
3149        }
3150
3151        /**
3152         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
3153         * Any content drawn by this method will be drawn after the item views are drawn
3154         * and will thus appear over the views.
3155         *
3156         * @param c Canvas to draw into
3157         */
3158        public void onDrawOver(Canvas c) {
3159        }
3160
3161        /**
3162         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
3163         * the number of pixels that the item view should be inset by, similar to padding or margin.
3164         * The default implementation sets the bounds of outRect to 0 and returns.
3165         *
3166         * <p>If this ItemDecoration does not affect the positioning of item views it should set
3167         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
3168         * before returning.</p>
3169         *
3170         * @param outRect Rect to receive the output.
3171         * @param itemPosition Adapter position of the item to offset
3172         */
3173        public void getItemOffsets(Rect outRect, int itemPosition) {
3174            outRect.set(0, 0, 0, 0);
3175        }
3176    }
3177
3178    /**
3179     * An OnItemTouchListener allows the application to intercept touch events in progress at the
3180     * view hierarchy level of the RecyclerView before those touch events are considered for
3181     * RecyclerView's own scrolling behavior.
3182     *
3183     * <p>This can be useful for applications that wish to implement various forms of gestural
3184     * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
3185     * a touch interaction already in progress even if the RecyclerView is already handling that
3186     * gesture stream itself for the purposes of scrolling.</p>
3187     */
3188    public interface OnItemTouchListener {
3189        /**
3190         * Silently observe and/or take over touch events sent to the RecyclerView
3191         * before they are handled by either the RecyclerView itself or its child views.
3192         *
3193         * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
3194         * in the order in which each listener was added, before any other touch processing
3195         * by the RecyclerView itself or child views occurs.</p>
3196         *
3197         * @param e MotionEvent describing the touch event. All coordinates are in
3198         *          the RecyclerView's coordinate system.
3199         * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
3200         *         to continue with the current behavior and continue observing future events in
3201         *         the gesture.
3202         */
3203        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
3204
3205        /**
3206         * Process a touch event as part of a gesture that was claimed by returning true from
3207         * a previous call to {@link #onInterceptTouchEvent}.
3208         *
3209         * @param e MotionEvent describing the touch event. All coordinates are in
3210         *          the RecyclerView's coordinate system.
3211         */
3212        public void onTouchEvent(RecyclerView rv, MotionEvent e);
3213    }
3214
3215    /**
3216     * An OnScrollListener can be set on a RecyclerView to receive messages
3217     * when a scrolling event has occurred on that RecyclerView.
3218     *
3219     * @see RecyclerView#setOnScrollListener(OnScrollListener)
3220     */
3221    public interface OnScrollListener {
3222        public void onScrollStateChanged(int newState);
3223        public void onScrolled(int dx, int dy);
3224    }
3225
3226    /**
3227     * A RecyclerListener can be set on a RecyclerView to receive messages whenever
3228     * a view is recycled.
3229     *
3230     * @see RecyclerView#setRecyclerListener(RecyclerListener)
3231     */
3232    public interface RecyclerListener {
3233
3234        /**
3235         * This method is called whenever the view in the ViewHolder is recycled.
3236         *
3237         * @param holder The ViewHolder containing the view that was recycled
3238         */
3239        public void onViewRecycled(ViewHolder holder);
3240    }
3241
3242    /**
3243     * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
3244     *
3245     * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
3246     * potentially expensive {@link View#findViewById(int)} results.</p>
3247     *
3248     * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
3249     * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
3250     * their own custom ViewHolder implementations to store data that makes binding view contents
3251     * easier. Implementations should assume that individual item views will hold strong references
3252     * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
3253     * strong references to extra off-screen item views for caching purposes</p>
3254     */
3255    public static abstract class ViewHolder {
3256        public final View itemView;
3257
3258        int mPosition = NO_POSITION;
3259        long mItemId = NO_ID;
3260        int mItemViewType = INVALID_TYPE;
3261
3262        /**
3263         * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
3264         * are all valid.
3265         */
3266        static final int FLAG_BOUND = 1 << 0;
3267
3268        /**
3269         * The data this ViewHolder's view reflects is stale and needs to be rebound
3270         * by the adapter. mPosition and mItemId are consistent.
3271         */
3272        static final int FLAG_UPDATE = 1 << 1;
3273
3274        /**
3275         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
3276         * are not to be trusted and may no longer match the item view type.
3277         * This ViewHolder must be fully rebound to different data.
3278         */
3279        static final int FLAG_INVALID = 1 << 2;
3280
3281        /**
3282         * This ViewHolder points at data that represents an item previously removed from the
3283         * data set. Its view may still be used for things like outgoing animations.
3284         */
3285        static final int FLAG_REMOVED = 1 << 3;
3286
3287        /**
3288         * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
3289         * and is intended to keep views around during animations.
3290         */
3291        static final int FLAG_NOT_RECYCLABLE = 1 << 4;
3292
3293        private int mFlags;
3294
3295        // If non-null, view is currently considered scrap and may be reused for other data by the
3296        // scrap container.
3297        private Recycler mScrapContainer = null;
3298
3299        public ViewHolder(View itemView) {
3300            if (itemView == null) {
3301                throw new IllegalArgumentException("itemView may not be null");
3302            }
3303            this.itemView = itemView;
3304        }
3305
3306        public final int getPosition() {
3307            return mPosition;
3308        }
3309
3310        public final long getItemId() {
3311            return mItemId;
3312        }
3313
3314        public final int getItemViewType() {
3315            return mItemViewType;
3316        }
3317
3318        boolean isScrap() {
3319            return mScrapContainer != null;
3320        }
3321
3322        void unScrap() {
3323            mScrapContainer.unscrapView(this);
3324            mScrapContainer = null;
3325        }
3326
3327        void setScrapContainer(Recycler recycler) {
3328            mScrapContainer = recycler;
3329        }
3330
3331        boolean isInvalid() {
3332            return (mFlags & FLAG_INVALID) != 0;
3333        }
3334
3335        boolean needsUpdate() {
3336            return (mFlags & FLAG_UPDATE) != 0;
3337        }
3338
3339        boolean isBound() {
3340            return (mFlags & FLAG_BOUND) != 0;
3341        }
3342
3343        boolean isRemoved() {
3344            return (mFlags & FLAG_REMOVED) != 0;
3345        }
3346
3347        void setFlags(int flags, int mask) {
3348            mFlags = (mFlags & ~mask) | (flags & mask);
3349        }
3350
3351        void addFlags(int flags) {
3352            mFlags |= flags;
3353        }
3354
3355        void clearFlagsForSharedPool() {
3356            mFlags = 0;
3357        }
3358
3359        @Override
3360        public String toString() {
3361            final StringBuilder sb = new StringBuilder("ViewHolder{" +
3362                    Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId);
3363            if (isScrap()) sb.append(" scrap");
3364            if (isInvalid()) sb.append(" invalid");
3365            if (!isBound()) sb.append(" unbound");
3366            if (needsUpdate()) sb.append(" update");
3367            if (isRemoved()) sb.append(" removed");
3368            sb.append("}");
3369            return sb.toString();
3370        }
3371
3372        public final void setIsRecyclable(boolean recyclable) {
3373            // TODO: might want this to be a refcount instead
3374            if (recyclable) {
3375                mFlags &= ~FLAG_NOT_RECYCLABLE;
3376            } else {
3377                mFlags |= FLAG_NOT_RECYCLABLE;
3378            }
3379        }
3380
3381        public final boolean isRecyclable() {
3382            return (mFlags & FLAG_NOT_RECYCLABLE) == 0 ||
3383                    !ViewCompat.hasTransientState(itemView);
3384        }
3385    }
3386
3387    /**
3388     * Queued operation to happen when child views are updated.
3389     */
3390    private static class UpdateOp {
3391        public static final int ADD = 0;
3392        public static final int REMOVE = 1;
3393        public static final int UPDATE = 2;
3394
3395        static final int POOL_SIZE = 30;
3396
3397        public int cmd;
3398        public int positionStart;
3399        public int itemCount;
3400
3401        public UpdateOp(int cmd, int positionStart, int itemCount) {
3402            this.cmd = cmd;
3403            this.positionStart = positionStart;
3404            this.itemCount = itemCount;
3405        }
3406    }
3407
3408    UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
3409        UpdateOp op = mUpdateOpPool.acquire();
3410        if (op == null) {
3411            op = new UpdateOp(cmd, positionStart, itemCount);
3412        } else {
3413            op.cmd = cmd;
3414            op.positionStart = positionStart;
3415            op.itemCount = itemCount;
3416        }
3417        return op;
3418    }
3419
3420    void recycleUpdateOp(UpdateOp op) {
3421        mUpdateOpPool.release(op);
3422    }
3423
3424    /**
3425     * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
3426     * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
3427     * to create their own subclass of this <code>LayoutParams</code> class
3428     * to store any additional required per-child view metadata about the layout.
3429     */
3430    public static class LayoutParams extends MarginLayoutParams {
3431        ViewHolder mViewHolder;
3432
3433        public LayoutParams(Context c, AttributeSet attrs) {
3434            super(c, attrs);
3435        }
3436
3437        public LayoutParams(int width, int height) {
3438            super(width, height);
3439        }
3440
3441        public LayoutParams(MarginLayoutParams source) {
3442            super(source);
3443        }
3444
3445        public LayoutParams(ViewGroup.LayoutParams source) {
3446            super(source);
3447        }
3448
3449        public LayoutParams(LayoutParams source) {
3450            super((ViewGroup.LayoutParams) source);
3451        }
3452
3453        /**
3454         * Returns true if the view this LayoutParams is attached to needs to have its content
3455         * updated from the corresponding adapter.
3456         *
3457         * @return true if the view should have its content updated
3458         */
3459        public boolean viewNeedsUpdate() {
3460            return mViewHolder.needsUpdate();
3461        }
3462
3463        /**
3464         * Returns true if the view this LayoutParams is attached to is now representing
3465         * potentially invalid data. A LayoutManager should scrap/recycle it.
3466         *
3467         * @return true if the view is invalid
3468         */
3469        public boolean isViewInvalid() {
3470            return mViewHolder.isInvalid();
3471        }
3472
3473        /**
3474         * Returns true if the adapter data item corresponding to the view this LayoutParams
3475         * is attached to has been removed from the data set. A LayoutManager may choose to
3476         * treat it differently in order to animate its outgoing or disappearing state.
3477         *
3478         * @return true if the item the view corresponds to was removed from the data set
3479         */
3480        public boolean isItemRemoved() {
3481            return mViewHolder.isRemoved();
3482        }
3483
3484        /**
3485         * Returns the position that the view this LayoutParams is attached to corresponds to.
3486         *
3487         * @return the adapter position this view was bound from
3488         */
3489        public int getViewPosition() {
3490            return mViewHolder.getPosition();
3491        }
3492    }
3493
3494    /**
3495     * Observer base class for watching changes to an {@link Adapter}.
3496     * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
3497     */
3498    public static abstract class AdapterDataObserver {
3499        public void onChanged() {
3500            // Do nothing
3501        }
3502
3503        public void onItemRangeChanged(int positionStart, int itemCount) {
3504            // do nothing
3505        }
3506
3507        public void onItemRangeInserted(int positionStart, int itemCount) {
3508            // do nothing
3509        }
3510
3511        public void onItemRangeRemoved(int positionStart, int itemCount) {
3512            // do nothing
3513        }
3514    }
3515
3516    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
3517        public boolean hasObservers() {
3518            return !mObservers.isEmpty();
3519        }
3520
3521        public void notifyChanged() {
3522            // since onChanged() is implemented by the app, it could do anything, including
3523            // removing itself from {@link mObservers} - and that could cause problems if
3524            // an iterator is used on the ArrayList {@link mObservers}.
3525            // to avoid such problems, just march thru the list in the reverse order.
3526            for (int i = mObservers.size() - 1; i >= 0; i--) {
3527                mObservers.get(i).onChanged();
3528            }
3529        }
3530
3531        public void notifyItemRangeChanged(int positionStart, int itemCount) {
3532            // since onItemRangeChanged() is implemented by the app, it could do anything, including
3533            // removing itself from {@link mObservers} - and that could cause problems if
3534            // an iterator is used on the ArrayList {@link mObservers}.
3535            // to avoid such problems, just march thru the list in the reverse order.
3536            for (int i = mObservers.size() - 1; i >= 0; i--) {
3537                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
3538            }
3539        }
3540
3541        public void notifyItemRangeInserted(int positionStart, int itemCount) {
3542            // since onItemRangeInserted() is implemented by the app, it could do anything,
3543            // including removing itself from {@link mObservers} - and that could cause problems if
3544            // an iterator is used on the ArrayList {@link mObservers}.
3545            // to avoid such problems, just march thru the list in the reverse order.
3546            for (int i = mObservers.size() - 1; i >= 0; i--) {
3547                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
3548            }
3549        }
3550
3551        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
3552            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
3553            // removing itself from {@link mObservers} - and that could cause problems if
3554            // an iterator is used on the ArrayList {@link mObservers}.
3555            // to avoid such problems, just march thru the list in the reverse order.
3556            for (int i = mObservers.size() - 1; i >= 0; i--) {
3557                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
3558            }
3559        }
3560    }
3561}
3562