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