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