• 热门专题

解决RecyclerView无法onItemClick问题

作者:  发布日期:2014-07-04 22:40:51
Tag标签:问题  
  • 对于RecyclerView的使用,大家可以查看将替代ListView的RecyclerView 的使用(一),单单从代码结构来说RecyclerView确实比ListView优化了很多,也简化了我们编写代码量,但是有一个问题会导致开发者不会去用它,更比说替换ListView了,我不知道使用过RecyclerView的人有没有进一步查看,RecyclerView没有提供Item的点击事件,我们使用列表不仅仅为了显示数据,同时也可以能会交互,所以RecyclerView这个问题导致基本没有人用它,我清楚谷歌是怎么想的,不过RecyclerView也并没有把所有的路给堵死,需要我们写代码来实现Item的点击事件,我们都知道RecyclerView里面新加了ViewHolder这个静态抽象类,这个类里面有一个方法getPosition()可以返回当前ViewHolder实例的位置,实现onItemClick就是使用它来做的,下面有两种方法来实现:

    第一种:不修改源码

    这种方法不修改源码,问题是只能在RecyclerView.Adapter中实现ItemClick事件

    public static class ViewHolder extends RecyclerView.ViewHolder {
    		public ViewHolder(View itemView) {
    			super(itemView);
    			itemView.setOnClickListener(new OnClickListener() {
    				@Override
    				public void onClick(View v) {
    					Log.e("jwzhangjie", "当前点击的位置:"+getPosition());
    				}
    			});
    			
    		}
    	}

    这种方式直观上看起来不太好,不过也可以实现ItemClick事件。

    第二种方法:修改RecyclerView源码

    1、把在RecyClerView类里面定义OnItemClickListener接口

    /**
         * Interface definition for a callback to be invoked when an item in this
         * RecyclerView.Adapter has been clicked.
         */
        public interface OnItemClickListener {
    
            /**
             * Callback method to be invoked when an item in this RecyclerView.Adapter has
             * been clicked.
             * <p>
             * Implementers can call getPosition(position) if they need
             * to access the data associated with the selected item.
             *
             * @param view The view within the RecyclerView.Adapter that was clicked (this
             *            will be a view provided by the adapter)
             * @param position The position of the view in the adapter.
             */
            void onItemClick(View view, int position);
        }
        
        public static OnItemClickListener mOnItemClickListener = null;
        
        /**
         * Register a callback to be invoked when an item in this AdapterView has
         * been clicked.
         *
         * @param listener The callback that will be invoked.
         */
        public void setOnItemClickListener(OnItemClickListener listener) {
            mOnItemClickListener = listener;
        }
    
        /**
         * @return The callback to be invoked with an item in this AdapterView has
         *         been clicked, or null id no callback has been set.
         */
        public final OnItemClickListener getOnItemClickListener() {
            return mOnItemClickListener;
        }
    

    2、在RecyclerView中的抽象类ViewHolder中添加View的点击事件

    public static abstract class ViewHolder implements OnClickListener{
            public final View itemView;
    
            int mPosition = NO_POSITION;
            int mOldPosition = NO_POSITION;
            long mItemId = NO_ID;
            int mItemViewType = INVALID_TYPE;
    
            /**
             * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
             * are all valid.
             */
            static final int FLAG_BOUND = 1 << 0;
    
            /**
             * The data this ViewHolder's view reflects is stale and needs to be rebound
             * by the adapter. mPosition and mItemId are consistent.
             */
            static final int FLAG_UPDATE = 1 << 1;
    
            /**
             * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
             * are not to be trusted and may no longer match the item view type.
             * This ViewHolder must be fully rebound to different data.
             */
            static final int FLAG_INVALID = 1 << 2;
    
            /**
             * This ViewHolder points at data that represents an item previously removed from the
             * data set. Its view may still be used for things like outgoing animations.
             */
            static final int FLAG_REMOVED = 1 << 3;
    
            /**
             * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
             * and is intended to keep views around during animations.
             */
            static final int FLAG_NOT_RECYCLABLE = 1 << 4;
    
            private int mFlags;
    
            private int mIsRecyclableCount = 0;
    
            // If non-null, view is currently considered scrap and may be reused for other data by the
            // scrap container.
            private Recycler mScrapContainer = null;
            
            @Override
    		public void onClick(View v) {
            	if (mOnItemClickListener != null) {
            		mOnItemClickListener.onItemClick(itemView, getPosition());
    			}
    		}
    
    		public ViewHolder(View itemView) {
                if (itemView == null) {
                    throw new IllegalArgumentException("itemView may not be null");
                }
                this.itemView = itemView;
                this.itemView.setOnClickListener(this);
            }
    
            void offsetPosition(int offset) {
                if (mOldPosition == NO_POSITION) {
                    mOldPosition = mPosition;
                }
                mPosition += offset;
            }
    
            void clearOldPosition() {
                mOldPosition = NO_POSITION;
            }
    
            public final int getPosition() {
                return mOldPosition == NO_POSITION ? mPosition : mOldPosition;
            }
    
            public final long getItemId() {
                return mItemId;
            }
    
            public final int getItemViewType() {
                return mItemViewType;
            }
    
            boolean isScrap() {
                return mScrapContainer != null;
            }
    
            void unScrap() {
                mScrapContainer.unscrapView(this);
                mScrapContainer = null;
            }
    
            void setScrapContainer(Recycler recycler) {
                mScrapContainer = recycler;
            }
    
            boolean isInvalid() {
                return (mFlags & FLAG_INVALID) != 0;
            }
    
            boolean needsUpdate() {
                return (mFlags & FLAG_UPDATE) != 0;
            }
    
            boolean isBound() {
                return (mFlags & FLAG_BOUND) != 0;
            }
    
            boolean isRemoved() {
                return (mFlags & FLAG_REMOVED) != 0;
            }
    
            void setFlags(int flags, int mask) {
                mFlags = (mFlags & ~mask) | (flags & mask);
            }
    
            void addFlags(int flags) {
                mFlags |= flags;
            }
    
            void clearFlagsForSharedPool() {
                mFlags = 0;
            }
    
            @Override
            public String toString() {
                final StringBuilder sb = new StringBuilder("ViewHolder{" +
                        Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId);
                if (isScrap()) sb.append(" scrap");
                if (isInvalid()) sb.append(" invalid");
                if (!isBound()) sb.append(" unbound");
                if (needsUpdate()) sb.append(" update");
                if (isRemoved()) sb.append(" removed");
                sb.append("}");
                return sb.toString();
            }

    3、完成上面的步骤,就可以使用RecyclerView来完成itemClick事件了

    cashAccountList.setOnItemClickListener(new OnItemClickListener() {
    			
    			@Override
    			public void onItemClick(View view, int position) {
    				AppLog.e("position: "+position);
    			}
    		});

    下面是完整的RecyclerView源码:

    /*
     * Copyright (C) 2013 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    
    package android.support.v7.widget;
    
    import android.content.Context;
    import android.database.Observable;
    import android.graphics.Canvas;
    import android.graphics.PointF;
    import android.graphics.Rect;
    import android.os.Build;
    import android.os.Parcel;
    import android.os.Parcelable;
    import android.support.annotation.Nullable;
    import android.support.v4.util.ArrayMap;
    import android.support.v4.util.Pools;
    import android.support.v4.view.MotionEventCompat;
    import android.support.v4.view.VelocityTrackerCompat;
    import android.support.v4.view.ViewCompat;
    import android.support.v4.widget.EdgeEffectCompat;
    import android.support.v4.widget.ScrollerCompat;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.SparseArray;
    import android.util.SparseIntArray;
    import android.view.FocusFinder;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    import android.view.animation.Interpolator;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * A flexible view for providing a limited window into a large data set.
     *
     * <h3>Glossary of terms:</h3>
     *
     * <ul>
     *     <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
     *     that represent items in a data set.</li>
     *     <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
     *     <li><em>Index:</em> The index of an attached child view as used in a call to
     *     {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
     *     <li><em>Binding:</em> The process of preparing a child view to display data corresponding
     *     to a <em>position</em> within the adapter.</li>
     *     <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
     *     position may be placed in a cache for later reuse to display the same type of data again
     *     later. This can drastically improve performance by skipping initial layout inflation
     *     or construction.</li>
     *     <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
     *     state during layout. Scrap views may be reused without becoming fully detached
     *     from the parent RecyclerView, either unmodified if no rebinding is required or modified
     *     by the adapter if the view was considered <em>dirty</em>.</li>
     *     <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
     *     being displayed.</li>
     * </ul>
     */
    public class RecyclerView extends ViewGroup {
        private static final String TAG = "RecyclerView";
    
        private static final boolean DEBUG = false;
        private static final boolean ENABLE_PREDICTIVE_ANIMATIONS = false;
    
        private static final boolean DISPATCH_TEMP_DETACH = false;
        public static final int HORIZONTAL = 0;
        public static final int VERTICAL = 1;
    
        public static final int NO_POSITION = -1;
        public static final long NO_ID = -1;
        public static final int INVALID_TYPE = -1;
    
        private static final int MAX_SCROLL_DURATION = 2000;
    
        private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    
        private final Recycler mRecycler = new Recycler();
    
        private SavedState mPendingSavedState;
    
        /**
         * Note: this Runnable is only ever posted if:
         * 1) We've been through first layout
         * 2) We know we have a fixed size (mHasFixedSize)
         * 3) We're attached
         */
        private final Runnable mUpdateChildViewsRunnable = new Runnable() {
            public void run() {
                if (mPendingUpdates.isEmpty()) {
                    return;
                }
                eatRequestLayout();
                updateChildViews();
                resumeRequestLayout(true);
            }
        };
    
        private final Rect mTempRect = new Rect();
    
        private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
        private final ArrayList<UpdateOp> mPendingLayoutUpdates = new ArrayList<UpdateOp>();
        private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
    
        private Adapter mAdapter;
        private LayoutManager mLayout;
        private RecyclerListener mRecyclerListener;
        private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
        private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
                new ArrayList<OnItemTouchListener>();
        private OnItemTouchListener mActiveOnItemTouchListener;
        private boolean mIsAttached;
        private boolean mHasFixedSize;
        private boolean mFirstLayoutComplete;
        private boolean mEatRequestLayout;
        private boolean mLayoutRequestEaten;
        private boolean mAdapterUpdateDuringMeasure;
        private final boolean mPostUpdatesOnAnimation;
    
        private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
    
        ItemAnimator mItemAnimator = new DefaultItemAnimator();
    
        private static final int INVALID_POINTER = -1;
    
        /**
         * The RecyclerView is not currently scrolling.
         * @see #getScrollState()
         */
        public static final int SCROLL_STATE_IDLE = 0;
    
        /**
         * The RecyclerView is currently being dragged by outside input such as user touch input.
         * @see #getScrollState()
         */
        public static final int SCROLL_STATE_DRAGGING = 1;
    
        /**
         * The RecyclerView is currently animating to a final position while not under
         * outside control.
         * @see #getScrollState()
         */
        public static final int SCROLL_STATE_SETTLING = 2;
    
        // Touch/scrolling handling
    
        private int mScrollState = SCROLL_STATE_IDLE;
        private int mScrollPointerId = INVALID_POINTER;
        private VelocityTracker mVelocityTracker;
        private int mInitialTouchX;
        private int mInitialTouchY;
        private int mLastTouchX;
        private int mLastTouchY;
        private final int mTouchSlop;
        private final int mMinFlingVelocity;
        private final int mMaxFlingVelocity;
    
        private final ViewFlinger mViewFlinger = new ViewFlinger();
    
        private final State mState = new State();
    
        private OnScrollListener mScrollListener;
    
        // For use in item animations
        boolean mItemsAddedOrRemoved = false;
        boolean mItemsChanged = false;
        int mAnimatingViewIndex = -1;
        int mNumAnimatingViews = 0;
        boolean mInPreLayout = false;
        private ItemAnimator.ItemAnimatorListener mItemAnimatorListener =
                new ItemAnimatorRestoreListener();
        private boolean mPostedAnimatorRunner = false;
        private Runnable mItemAnimatorRunner = new Runnable() {
            @Override
            public void run() {
                if (mItemAnimator != null) {
                    mItemAnimator.runPendingAnimations();
                }
                mPostedAnimatorRunner = false;
            }
        };
    
        private static final Interpolator sQuinticInterpolator = new Interpolator() {
            public float getInterpolation(float t) {
                t -= 1.0f;
                return t * t * t * t * t + 1.0f;
            }
        };
    
        public RecyclerView(Context context) {
            this(context, null);
        }
    
        public RecyclerView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
    
            final int version = Build.VERSION.SDK_INT;
            mPostUpdatesOnAnimation = version >= 16;
    
            final ViewConfiguration vc = ViewConfiguration.get(context);
            mTouchSlop = vc.getScaledTouchSlop();
            mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
            mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
            setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
    
            mItemAnimator.setListener(mItemAnimatorListener);
        }
    
        /**
         * RecyclerView can perform several optimizations if it can know in advance that changes in
         * adapter content cannot change the size of the RecyclerView itself.
         * If your use of RecyclerView falls into this category, set this to true.
         *
         * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
         */
        public void setHasFixedSize(boolean hasFixedSize) {
            mHasFixedSize = hasFixedSize;
        }
    
        /**
         * @return true if the app has specified that changes in adapter content cannot change
         * the size of the RecyclerView itself.
         */
        public boolean hasFixedSize() {
            return mHasFixedSize;
        }
    
        /**
         * Set a new adapter to provide child views on demand.
         *
         * @param adapter The new adapter to set, or null to set no adapter.
         */
        public void setAdapter(Adapter adapter) {
            if (mAdapter != null) {
                mAdapter.unregisterAdapterDataObserver(mObserver);
            }
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            // Since animations are ended, mLayout.children should be equal to recyclerView.children.
            // This may not be true if item animator's end does not work as expected. (e.g. not release
            // children instantly). It is safer to use mLayout's child count.
            if (mLayout != null) {
                mLayout.removeAndRecycleAllViews(mRecycler);
                mLayout.removeAndRecycleScrapInt(mRecycler, true);
            }
    
            final Adapter oldAdapter = mAdapter;
            mAdapter = adapter;
            if (adapter != null) {
                adapter.registerAdapterDataObserver(mObserver);
            }
            if (mLayout != null) {
                mLayout.onAdapterChanged(oldAdapter, mAdapter);
            }
            mRecycler.onAdapterChanged(oldAdapter, mAdapter);
            mState.mStructureChanged = true;
            markKnownViewsInvalid();
            requestLayout();
        }
    
        /**
         * Retrieves the previously set adapter or null if no adapter is set.
         *
         * @return The previously set adapter
         * @see #setAdapter(Adapter)
         */
        public Adapter getAdapter() {
            return mAdapter;
        }
    
        /**
         * Register a listener that will be notified whenever a child view is recycled.
         *
         * <p>This listener will be called when a LayoutManager or the RecyclerView decides
         * that a child view is no longer needed. If an application associates expensive
         * or heavyweight data with item views, this may be a good place to release
         * or free those resources.</p>
         *
         * @param listener Listener to register, or null to clear
         */
        public void setRecyclerListener(RecyclerListener listener) {
            mRecyclerListener = listener;
        }
    
        /**
         * Set the {@link LayoutManager} that this RecyclerView will use.
         *
         * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
         * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
         * layout arrangements for child views. These arrangements are controlled by the
         * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
         *
         * <p>Several default strategies are provided for common uses such as lists and grids.</p>
         *
         * @param layout LayoutManager to use
         */
        public void setLayoutManager(LayoutManager layout) {
            if (layout == mLayout) {
                return;
            }
    
            mRecycler.clear();
            removeAllViews();
            if (mLayout != null) {
                if (mIsAttached) {
                    mLayout.onDetachedFromWindow(this);
                }
                mLayout.mRecyclerView = null;
            }
            mLayout = layout;
            if (layout != null) {
                if (layout.mRecyclerView != null) {
                    throw new IllegalArgumentException("LayoutManager " + layout +
                            " is already attached to a RecyclerView: " + layout.mRecyclerView);
                }
                layout.mRecyclerView = this;
                if (mIsAttached) {
                    mLayout.onAttachedToWindow(this);
                }
            }
            requestLayout();
        }
    
        @Override
        protected Parcelable onSaveInstanceState() {
            SavedState state = new SavedState(super.onSaveInstanceState());
            if (mPendingSavedState != null) {
                state.copyFrom(mPendingSavedState);
            } else if (mLayout != null) {
                state.mLayoutState = mLayout.onSaveInstanceState();
            } else {
                state.mLayoutState = null;
            }
    
            return state;
        }
    
        @Override
        protected void onRestoreInstanceState(Parcelable state) {
            mPendingSavedState = (SavedState) state;
            super.onRestoreInstanceState(mPendingSavedState.getSuperState());
            if (mLayout != null && mPendingSavedState.mLayoutState != null) {
                mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
            }
        }
    
        /**
         * Adds a view to the animatingViews list.
         * mAnimatingViews holds the child views that are currently being kept around
         * purely for the purpose of being animated out of view. They are drawn as a regular
         * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
         * as they are managed separately from the regular child views.
         * @param view The view to be removed
         */
        private void addAnimatingView(View view) {
            boolean alreadyAdded = false;
            if (mNumAnimatingViews > 0) {
                for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) {
                    if (getChildAt(i) == view) {
                        alreadyAdded = true;
                        break;
                    }
                }
            }
            if (!alreadyAdded) {
                if (mNumAnimatingViews == 0) {
                    mAnimatingViewIndex = getChildCount();
                }
                ++mNumAnimatingViews;
                addView(view);
            }
            mRecycler.unscrapView(getChildViewHolder(view));
        }
    
        /**
         * Removes a view from the animatingViews list.
         * @param view The view to be removed
         * @see #addAnimatingView(View)
         */
        private void removeAnimatingView(View view) {
            if (mNumAnimatingViews > 0) {
                for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) {
                    if (getChildAt(i) == view) {
                        removeViewAt(i);
                        --mNumAnimatingViews;
                        if (mNumAnimatingViews == 0) {
                            mAnimatingViewIndex = -1;
                        }
                        mRecycler.recycleView(view);
                        return;
                    }
                }
            }
        }
    
        private View getAnimatingView(int position, int type) {
            if (mNumAnimatingViews > 0) {
                for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) {
                    final View view = getChildAt(i);
                    ViewHolder holder = getChildViewHolder(view);
                    if (holder.getPosition() == position &&
                            ( type == INVALID_TYPE || holder.getItemViewType() == type)) {
                        return view;
                    }
                }
            }
            return null;
        }
    
        /**
         * Return the {@link LayoutManager} currently responsible for
         * layout policy for this RecyclerView.
         *
         * @return The currently bound LayoutManager
         */
        public LayoutManager getLayoutManager() {
            return mLayout;
        }
    
        /**
         * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
         * if no pool is set for this view a new one will be created. See
         * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
         *
         * @return The pool used to store recycled item views for reuse.
         * @see #setRecycledViewPool(RecycledViewPool)
         */
        public RecycledViewPool getRecycledViewPool() {
            return mRecycler.getRecycledViewPool();
        }
    
        /**
         * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
         * This can be useful if you have multiple RecyclerViews with adapters that use the same
         * view types, for example if you have several data sets with the same kinds of item views
         * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
         *
         * @param pool Pool to set. If this parameter is null a new pool will be created and used.
         */
        public void setRecycledViewPool(RecycledViewPool pool) {
            mRecycler.setRecycledViewPool(pool);
        }
    
        /**
         * Set the number of offscreen views to retain before adding them to the potentially shared
         * {@link #getRecycledViewPool() recycled view pool}.
         *
         * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
         * a LayoutManager to reuse those views unmodified without needing to return to the adapter
         * to rebind them.</p>
         *
         * @param size Number of views to cache offscreen before returning them to the general
         *             recycled view pool
         */
        public void setItemViewCacheSize(int size) {
            mRecycler.setViewCacheSize(size);
        }
    
        /**
         * Return the current scrolling state of the RecyclerView.
         *
         * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
         * {@link #SCROLL_STATE_SETTLING}
         */
        public int getScrollState() {
            return mScrollState;
        }
    
        private void setScrollState(int state) {
            if (state == mScrollState) {
                return;
            }
            mScrollState = state;
            if (state != SCROLL_STATE_SETTLING) {
                stopScroll();
            }
            if (mScrollListener != null) {
                mScrollListener.onScrollStateChanged(state);
            }
        }
    
        /**
         * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
         * affect both measurement and drawing of individual item views.
         *
         * <p>Item decorations are ordered. Decorations placed earlier in the list will
         * be run/queried/drawn first for their effects on item views. Padding added to views
         * will be nested; a padding added by an earlier decoration will mean further
         * item decorations in the list will be asked to draw/pad within the previous decoration's
         * given area.</p>
         *
         * @param decor Decoration to add
         * @param index Position in the decoration chain to insert this decoration at. If this value
         *              is negative the decoration will be added at the end.
         */
        public void addItemDecoration(ItemDecoration decor, int index) {
            if (mItemDecorations.isEmpty()) {
                setWillNotDraw(false);
            }
            if (index < 0) {
                mItemDecorations.add(decor);
            } else {
                mItemDecorations.add(index, decor);
            }
            markItemDecorInsetsDirty();
            requestLayout();
        }
    
        /**
         * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
         * affect both measurement and drawing of individual item views.
         *
         * <p>Item decorations are ordered. Decorations placed earlier in the list will
         * be run/queried/drawn first for their effects on item views. Padding added to views
         * will be nested; a padding added by an earlier decoration will mean further
         * item decorations in the list will be asked to draw/pad within the previous decoration's
         * given area.</p>
         *
         * @param decor Decoration to add
         */
        public void addItemDecoration(ItemDecoration decor) {
            addItemDecoration(decor, -1);
        }
    
        /**
         * Remove an {@link ItemDecoration} from this RecyclerView.
         *
         * <p>The given decoration will no longer impact the measurement and drawing of
         * item views.</p>
         *
         * @param decor Decoration to remove
         * @see #addItemDecoration(ItemDecoration)
         */
        public void removeItemDecoration(ItemDecoration decor) {
            mItemDecorations.remove(decor);
            if (mItemDecorations.isEmpty()) {
                setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
            }
            markItemDecorInsetsDirty();
            requestLayout();
        }
    
        /**
         * Set a listener that will be notified of any changes in scroll state or position.
         *
         * @param listener Listener to set or null to clear
         */
        public void setOnScrollListener(OnScrollListener listener) {
            mScrollListener = listener;
        }
    
        /**
         * Convenience method to scroll to a certain position.
         *
         * RecyclerView does not implement scrolling logic, rather forwards the call to
         * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
         * @param position Scroll to this adapter position
         * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
         */
        public void scrollToPosition(int position) {
            stopScroll();
            mLayout.scrollToPosition(position);
            awakenScrollBars();
        }
    
        /**
         * Starts a smooth scroll to an adapter position.
         * <p>
         * To support smooth scrolling, you must override
         * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
         * {@link SmoothScroller}.
         * <p>
         * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
         * provide a custom smooth scroll logic, override
         * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
         * LayoutManager.
         *
         * @param position The adapter position to scroll to
         * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
         */
        public void smoothScrollToPosition(int position) {
            mLayout.smoothScrollToPosition(this, mState, position);
        }
    
        @Override
        public void scrollTo(int x, int y) {
            throw new UnsupportedOperationException(
                    "RecyclerView does not support scrolling to an absolute position.");
        }
    
        @Override
        public void scrollBy(int x, int y) {
            if (mLayout == null) {
                throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
                        "Call setLayoutManager with a non-null argument.");
            }
            final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
            final boolean canScrollVertical = mLayout.canScrollVertically();
            if (canScrollHorizontal || canScrollVertical) {
                scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0);
            }
        }
    
        /**
         * Helper method reflect data changes to the state.
         * <p>
         * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
         * but data actually changed.
         * <p>
         * This method consumes all deferred changes to avoid that case.
         * <p>
         * This also ends all pending animations. It will be changed once we can support
         * animations during scroll.
         */
        private void consumePendingUpdateOperations() {
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            if (mPendingUpdates.size() > 0) {
                mUpdateChildViewsRunnable.run();
            }
        }
    
        /**
         * Does not perform bounds checking. Used by internal methods that have already validated input.
         */
        void scrollByInternal(int x, int y) {
            int overscrollX = 0, overscrollY = 0;
            consumePendingUpdateOperations();
            if (mAdapter != null) {
                eatRequestLayout();
                if (x != 0) {
                    final int hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                    overscrollX = x - hresult;
                }
                if (y != 0) {
                    final int vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                    overscrollY = y - vresult;
                }
                resumeRequestLayout(false);
            }
    
            if (!mItemDecorations.isEmpty()) {
                invalidate();
            }
            if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
                pullGlows(overscrollX, overscrollY);
            }
            if (mScrollListener != null && (x != 0 || y != 0)) {
                mScrollListener.onScrolled(x, y);
            }
            if (!awakenScrollBars()) {
                invalidate();
            }
        }
    
        /**
         * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
         * range. This value is used to compute the length of the thumb within the scrollbar's track.
         * </p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
         * LayoutManager. </p>
         *
         * @return The horizontal offset of the scrollbar's thumb
         * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
         * (RecyclerView.Adapter)
         */
        @Override
        protected int computeHorizontalScrollOffset() {
            return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState)
                    : 0;
        }
    
        /**
         * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
         * horizontal range. This value is used to compute the length of the thumb within the
         * scrollbar's track.</p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
         * LayoutManager.</p>
         *
         * @return The horizontal extent of the scrollbar's thumb
         * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
         */
        @Override
        protected int computeHorizontalScrollExtent() {
            return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
        }
    
        /**
         * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
         * LayoutManager.</p>
         *
         * @return The total horizontal range represented by the vertical scrollbar
         * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
         */
        @Override
        protected int computeHorizontalScrollRange() {
            return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
        }
    
        /**
         * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
         * This value is used to compute the length of the thumb within the scrollbar's track. </p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
         * LayoutManager.</p>
         *
         * @return The vertical offset of the scrollbar's thumb
         * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
         * (RecyclerView.Adapter)
         */
        @Override
        protected int computeVerticalScrollOffset() {
            return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
        }
    
        /**
         * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
         * This value is used to compute the length of the thumb within the scrollbar's track.</p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
         * LayoutManager.</p>
         *
         * @return The vertical extent of the scrollbar's thumb
         * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
         */
        @Override
        protected int computeVerticalScrollExtent() {
            return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
        }
    
        /**
         * <p>Compute the vertical range that the vertical scrollbar represents.</p>
         *
         * <p>The range is expressed in arbitrary units that must be the same as the units used by
         * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
         *
         * <p>Default implementation returns 0.</p>
         *
         * <p>If you want to support scroll bars, override
         * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
         * LayoutManager.</p>
         *
         * @return The total vertical range represented by the vertical scrollbar
         * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
         */
        @Override
        protected int computeVerticalScrollRange() {
            return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
        }
    
    
        void eatRequestLayout() {
            if (!mEatRequestLayout) {
                mEatRequestLayout = true;
                mLayoutRequestEaten = false;
            }
        }
    
        void resumeRequestLayout(boolean performLayoutChildren) {
            if (mEatRequestLayout) {
                if (performLayoutChildren && mLayoutRequestEaten &&
                        mLayout != null && mAdapter != null) {
                    dispatchLayout();
                }
                mEatRequestLayout = false;
                mLayoutRequestEaten = false;
            }
        }
    
        /**
         * Animate a scroll by the given amount of pixels along either axis.
         *
         * @param dx Pixels to scroll horizontally
         * @param dy Pixels to scroll vertically
         */
        public void smoothScrollBy(int dx, int dy) {
            if (dx != 0 || dy != 0) {
                mViewFlinger.smoothScrollBy(dx, dy);
            }
        }
    
        /**
         * Begin a standard fling with an initial velocity along each axis in pixels per second.
         * If the velocity given is below the system-defined minimum this method will return false
         * and no fling will occur.
         *
         * @param velocityX Initial horizontal velocity in pixels per second
         * @param velocityY Initial vertical velocity in pixels per second
         * @return true if the fling was started, false if the velocity was too low to fling
         */
        public boolean fling(int velocityX, int velocityY) {
            if (Math.abs(velocityX) < mMinFlingVelocity) {
                velocityX = 0;
            }
            if (Math.abs(velocityY) < mMinFlingVelocity) {
                velocityY = 0;
            }
            velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
            velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
            if (velocityX != 0 || velocityY != 0) {
                mViewFlinger.fling(velocityX, velocityY);
                return true;
            }
            return false;
        }
    
        /**
         * Stop any current scroll in progress, such as one started by
         * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
         */
        public void stopScroll() {
            mViewFlinger.stop();
            mLayout.stopSmoothScroller();
        }
    
        /**
         * Apply a pull to relevant overscroll glow effects
         */
        private void pullGlows(int overscrollX, int overscrollY) {
            if (overscrollX < 0) {
                if (mLeftGlow == null) {
                    mLeftGlow = new EdgeEffectCompat(getContext());
                    mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                            getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
                }
                mLeftGlow.onPull(-overscrollX / (float) getWidth());
            } else if (overscrollX > 0) {
                if (mRightGlow == null) {
                    mRightGlow = new EdgeEffectCompat(getContext());
                    mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                            getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
                }
                mRightGlow.onPull(overscrollX / (float) getWidth());
            }
    
            if (overscrollY < 0) {
                if (mTopGlow == null) {
                    mTopGlow = new EdgeEffectCompat(getContext());
                    mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                            getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
                }
                mTopGlow.onPull(-overscrollY / (float) getHeight());
            } else if (overscrollY > 0) {
                if (mBottomGlow == null) {
                    mBottomGlow = new EdgeEffectCompat(getContext());
                    mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                            getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
                }
                mBottomGlow.onPull(overscrollY / (float) getHeight());
            }
    
            if (overscrollX != 0 || overscrollY != 0) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
        private void releaseGlows() {
            boolean needsInvalidate = false;
            if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease();
            if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease();
            if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease();
            if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease();
            if (needsInvalidate) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
        void absorbGlows(int velocityX, int velocityY) {
            if (velocityX < 0) {
                if (mLeftGlow == null) {
                    mLeftGlow = new EdgeEffectCompat(getContext());
                    mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                            getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
                }
                mLeftGlow.onAbsorb(-velocityX);
            } else if (velocityX > 0) {
                if (mRightGlow == null) {
                    mRightGlow = new EdgeEffectCompat(getContext());
                    mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                            getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
                }
                mRightGlow.onAbsorb(velocityX);
            }
    
            if (velocityY < 0) {
                if (mTopGlow == null) {
                    mTopGlow = new EdgeEffectCompat(getContext());
                    mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                            getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
                }
                mTopGlow.onAbsorb(-velocityY);
            } else if (velocityY > 0) {
                if (mBottomGlow == null) {
                    mBottomGlow = new EdgeEffectCompat(getContext());
                    mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                            getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
                }
                mBottomGlow.onAbsorb(velocityY);
            }
    
            if (velocityX != 0 || velocityY != 0) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
        // Focus handling
    
        @Override
        public View focusSearch(View focused, int direction) {
            View result = mLayout.onInterceptFocusSearch(focused, direction);
            if (result != null) {
                return result;
            }
            final FocusFinder ff = FocusFinder.getInstance();
            result = ff.findNextFocus(this, focused, direction);
            if (result == null && mAdapter != null) {
                eatRequestLayout();
                result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
                resumeRequestLayout(false);
            }
            return result != null ? result : super.focusSearch(focused, direction);
        }
    
        @Override
        public void requestChildFocus(View child, View focused) {
            if (!mLayout.onRequestChildFocus(this, child, focused)) {
                mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
                offsetDescendantRectToMyCoords(focused, mTempRect);
                offsetRectIntoDescendantCoords(child, mTempRect);
                requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
            }
            super.requestChildFocus(child, focused);
        }
    
        @Override
        public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
            return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
        }
    
        @Override
        public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
            if (!mLayout.onAddFocusables(this, views, direction, focusableMode)) {
                super.addFocusables(views, direction, focusableMode);
            }
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            mIsAttached = true;
            mFirstLayoutComplete = false;
            if (mLayout != null) {
                mLayout.onAttachedToWindow(this);
            }
            mPostedAnimatorRunner = false;
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            mFirstLayoutComplete = false;
    
            stopScroll();
            // TODO Mark what our target position was if relevant, then we can jump there
            // on reattach.
            mIsAttached = false;
            if (mLayout != null) {
                mLayout.onDetachedFromWindow(this);
            }
            removeCallbacks(mItemAnimatorRunner);
        }
    
        /**
         * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
         * to child views or this view's standard scrolling behavior.
         *
         * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
         * returns true from
         * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
         * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
         * for each incoming MotionEvent until the end of the gesture.</p>
         *
         * @param listener Listener to add
         */
        public void addOnItemTouchListener(OnItemTouchListener listener) {
            mOnItemTouchListeners.add(listener);
        }
    
        /**
         * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
         *
         * @param listener Listener to remove
         */
        public void removeOnItemTouchListener(OnItemTouchListener listener) {
            mOnItemTouchListeners.remove(listener);
            if (mActiveOnItemTouchListener == listener) {
                mActiveOnItemTouchListener = null;
            }
        }
    
        private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
            final int action = e.getAction();
            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
                mActiveOnItemTouchListener = null;
            }
    
            final int listenerCount = mOnItemTouchListeners.size();
            for (int i = 0; i < listenerCount; i++) {
                final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
                if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
                    mActiveOnItemTouchListener = listener;
                    return true;
                }
            }
            return false;
        }
    
        private boolean dispatchOnItemTouch(MotionEvent e) {
            final int action = e.getAction();
            if (mActiveOnItemTouchListener != null) {
                if (action == MotionEvent.ACTION_DOWN) {
                    // Stale state from a previous gesture, we're starting a new one. Clear it.
                    mActiveOnItemTouchListener = null;
                } else {
                    mActiveOnItemTouchListener.onTouchEvent(this, e);
                    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                        // Clean up for the next gesture.
                        mActiveOnItemTouchListener = null;
                    }
                    return true;
                }
            }
    
            // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
            // as called from onInterceptTouchEvent; skip it.
            if (action != MotionEvent.ACTION_DOWN) {
                final int listenerCount = mOnItemTouchListeners.size();
                for (int i = 0; i < listenerCount; i++) {
                    final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
                    if (listener.onInterceptTouchEvent(this, e)) {
                        mActiveOnItemTouchListener = listener;
                        return true;
                    }
                }
            }
            return false;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent e) {
            if (dispatchOnItemTouchIntercept(e)) {
                cancelTouch();
                return true;
            }
    
            final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
            final boolean canScrollVertically = mLayout.canScrollVertically();
    
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(e);
    
            final int action = MotionEventCompat.getActionMasked(e);
            final int actionIndex = MotionEventCompat.getActionIndex(e);
    
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                    mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                    mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
    
                    if (mScrollState == SCROLL_STATE_SETTLING) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                    break;
    
                case MotionEventCompat.ACTION_POINTER_DOWN:
                    mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
                    mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
                    mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
                    break;
    
                case MotionEvent.ACTION_MOVE: {
                    final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                    if (index < 0) {
                        Log.e(TAG, "Error processing scroll; pointer index for id " +
                                mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                        return false;
                    }
    
                    final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                    final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                    if (mScrollState != SCROLL_STATE_DRAGGING) {
                        final int dx = x - mInitialTouchX;
                        final int dy = y - mInitialTouchY;
                        boolean startScroll = false;
                        if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                            mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                            startScroll = true;
                        }
                        if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                            mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                            startScroll = true;
                        }
                        if (startScroll) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                            setScrollState(SCROLL_STATE_DRAGGING);
                        }
                    }
                } break;
    
                case MotionEventCompat.ACTION_POINTER_UP: {
                    onPointerUp(e);
                } break;
    
                case MotionEvent.ACTION_UP: {
                    mVelocityTracker.clear();
                } break;
    
                case MotionEvent.ACTION_CANCEL: {
                    cancelTouch();
                }
            }
            return mScrollState == SCROLL_STATE_DRAGGING;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent e) {
            if (dispatchOnItemTouch(e)) {
                cancelTouch();
                return true;
            }
    
            final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
            final boolean canScrollVertically = mLayout.canScrollVertically();
    
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(e);
    
            final int action = MotionEventCompat.getActionMasked(e);
            final int actionIndex = MotionEventCompat.getActionIndex(e);
    
            switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                    mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                    mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
                } break;
    
                case MotionEventCompat.ACTION_POINTER_DOWN: {
                    mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
                    mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
                    mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
                } break;
    
                case MotionEvent.ACTION_MOVE: {
                    final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                    if (index < 0) {
                        Log.e(TAG, "Error processing scroll; pointer index for id " +
                                mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                        return false;
                    }
    
                    final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                    final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                    if (mScrollState != SCROLL_STATE_DRAGGING) {
                        final int dx = x - mInitialTouchX;
                        final int dy = y - mInitialTouchY;
                        boolean startScroll = false;
                        if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                            mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                            startScroll = true;
                        }
                        if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                            mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                            startScroll = true;
                        }
                        if (startScroll) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                            setScrollState(SCROLL_STATE_DRAGGING);
                        }
                    }
                    if (mScrollState == SCROLL_STATE_DRAGGING) {
                        final int dx = x - mLastTouchX;
                        final int dy = y - mLastTouchY;
                        scrollByInternal(canScrollHorizontally ? -dx : 0,
                                canScrollVertically ? -dy : 0);
                    }
                    mLastTouchX = x;
                    mLastTouchY = y;
                } break;
    
                case MotionEventCompat.ACTION_POINTER_UP: {
                    onPointerUp(e);
                } break;
    
                case MotionEvent.ACTION_UP: {
                    mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                    final float xvel = canScrollHorizontally ?
                            -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
                    final float yvel = canScrollVertically ?
                            -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
                    if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                        setScrollState(SCROLL_STATE_IDLE);
                    }
                    mVelocityTracker.clear();
                    releaseGlows();
                } break;
    
                case MotionEvent.ACTION_CANCEL: {
                    cancelTouch();
                } break;
            }
    
            return true;
        }
    
        private void cancelTouch() {
            mVelocityTracker.clear();
            releaseGlows();
            setScrollState(SCROLL_STATE_IDLE);
        }
    
        private void onPointerUp(MotionEvent e) {
            final int actionIndex = MotionEventCompat.getActionIndex(e);
            if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) {
                // Pick a new pointer to pick up the slack.
                final int newIndex = actionIndex == 0 ? 1 : 0;
                mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex);
                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f);
            }
        }
    
        @Override
        protected void onMeasure(int widthSpec, int heightSpec) {
            if (mAdapterUpdateDuringMeasure) {
                eatRequestLayout();
                updateChildViews();
                mAdapterUpdateDuringMeasure = false;
                resumeRequestLayout(false);
            }
    
            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            }
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    
            final int widthSize = getMeasuredWidth();
            final int heightSize = getMeasuredHeight();
    
            if (mLeftGlow != null) mLeftGlow.setSize(heightSize, widthSize);
            if (mTopGlow != null) mTopGlow.setSize(widthSize, heightSize);
            if (mRightGlow != null) mRightGlow.setSize(heightSize, widthSize);
            if (mBottomGlow != null) mBottomGlow.setSize(widthSize, heightSize);
        }
    
        /**
         * Sets the {@link ItemAnimator} that will handle animations involving changes
         * to the items in this RecyclerView. By default, RecyclerView instantiates and
         * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
         * enabled for the RecyclerView depends on the ItemAnimator and whether
         * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
         * supports item animations}.
         *
         * @param animator The ItemAnimator being set. If null, no animations will occur
         * when changes occur to the items in this RecyclerView.
         */
        public void setItemAnimator(ItemAnimator animator) {
            if (mItemAnimator != null) {
                mItemAnimator.setListener(null);
            }
            mItemAnimator = animator;
            if (mItemAnimator != null) {
                mItemAnimator.setListener(mItemAnimatorListener);
            }
        }
    
        /**
         * Gets the current ItemAnimator for this RecyclerView. A null return value
         * indicates that there is no animator and that item changes will happen without
         * any animations. By default, RecyclerView instantiates and
         * uses an instance of {@link DefaultItemAnimator}.
         *
         * @return ItemAnimator The current ItemAnimator. If null, no animations will occur
         * when changes occur to the items in this RecyclerView.
         */
        public ItemAnimator getItemAnimator() {
            return mItemAnimator;
        }
    
        /**
         * Post a runnable to the next frame to run pending item animations. Only the first such
         * request will be posted, governed by the mPostedAnimatorRunner flag.
         */
        private void postAnimationRunner() {
            if (!mPostedAnimatorRunner && mIsAttached) {
                ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
                mPostedAnimatorRunner = true;
            }
        }
    
        private boolean predictiveItemAnimationsEnabled() {
            return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
        }
    
        /**
         * Wrapper around layoutChildren() that handles animating changes caused by layout.
         * Animations work on the assumption that there are five different kinds of items
         * in play:
         * PERSISTENT: items are visible before and after layout
         * REMOVED: items were visible before layout and were removed by the app
         * ADDED: items did not exist before layout and were added by the app
         * DISAPPEARING: items exist in the data set before/after, but changed from
         * visible to non-visible in the process of layout (they were moved off
         * screen as a side-effect of other changes)
         * APPEARING: items exist in the data set before/after, but changed from
         * non-visible to visible in the process of layout (they were moved on
         * screen as a side-effect of other changes)
         * The overall approach figures out what items exist before/after layout and
         * infers one of the five above states for each of the items. Then the animations
         * are set up accordingly:
         * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)})
         * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)})
         * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)})
         * DISAPPEARING views are moved off screen
         * APPEARING views are moved on screen
         */
        void dispatchLayout() {
            if (mAdapter == null) {
                Log.e(TAG, "No adapter attached; skipping layout");
                return;
            }
    
            eatRequestLayout();
    
            // simple animations are a subset of advanced animations (which will cause a
            // prelayout step)
            boolean animateChangesSimple = mItemAnimator != null && mItemsAddedOrRemoved
                    && !mItemsChanged;
            final boolean animateChangesAdvanced = ENABLE_PREDICTIVE_ANIMATIONS &&
                    animateChangesSimple && predictiveItemAnimationsEnabled();
            mItemsAddedOrRemoved = mItemsChanged = false;
            ArrayMap<View, Rect> appearingViewInitialBounds = null;
            mState.mInPreLayout = animateChangesAdvanced;
            mState.mItemCount = mAdapter.getItemCount();
    
            if (animateChangesSimple) {
                // Step 0: Find out where all non-removed items are, pre-layout
                mState.mPreLayoutHolderMap.clear();
                mState.mPostLayoutHolderMap.clear();
                int count = getChildCount();
                for (int i = 0; i < count; ++i) {
                    final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
                    final View view = holder.itemView;
                    mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
                            view.getLeft(), view.getTop(), view.getRight(), view.getBottom(),
                            holder.mPosition));
                }
            }
            if (animateChangesAdvanced) {
    
                // Step 1: run prelayout: This will use the old positions of items. The layout manager
                // is expected to layout everything, even removed items (though not to add removed
                // items back to the container). This gives the pre-layout position of APPEARING views
                // which come into existence as part of the real layout				
About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规