RecyclerView源码分析

Posted 小威少威

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RecyclerView源码分析相关的知识,希望对你有一定的参考价值。

    RecyclerView发布已经挺久了,博客上也有好多优秀的使用教程,相比较ListView,RecyclerView的使用确实更加灵活,本文就针对RecyclerView的源码进行分析,分析一下它的工作原理,关于它的使用教程,大家可以去看下hongyang大神的博文,以下我就自己针对源码的查看写了自己的分析,如有不对的地方,欢迎提出指教。
    我们首先看下RecyclerView这个类的第一条注释说明:A flexible view for providing a limited window into a large data set.可见该View的定义就是一种灵活的、能够装载大量数据的View。该类的源码有1w多行,我们就从使用它相关的代码进行查看:

recyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);
    recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
    recyclerView.setAdapter(new MyAdapter(this,datas));
    recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));

    这是我们常用的一段基本的代码,可知,我们会用到以下几个方法:setLayoutManager,(关键点,灵活的设置布局,简单),setAdapter,addItemDecoration。另外,还有这个类我们也一起看下:Recycler。

    关于Recycler:
    A Recycler is responsible for managing scrapped or detached item views for reufinal

 public final class Recycler 
            final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
            private ArrayList<ViewHolder> mChangedScrap = null;
            final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
            private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
            private int mViewCacheMax = DEFAULT_CACHE_SIZE;
            private RecycledViewPool mRecyclerPool;
            private ViewCacheExtension mViewCacheExtension;
            private static final int DEFAULT_CACHE_SIZE = 2;

    它为RecyclerView中的item views提供了复用,用于管理已经废弃或与RecyclerView分离的(scrapped or detached)item view。

/**
         * 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;
            
            stopScroll();
            // TODO We should do this switch a dispachLayout pass and animate children. There is a good
            // chance that LayoutManagers will re-use views.
            if (mLayout != null) 
                if (mIsAttached) 
                    mLayout.dispatchDetachedFromWindow(this, mRecycler);
                
                mLayout.setRecyclerView(null);
            
            mRecycler.clear();
            mChildHelper.removeAllViewsUnfiltered();
            mLayout = layout;
            if (layout != null) 
                if (layout.mRecyclerView != null) 
                    throw new IllegalArgumentException("LayoutManager " + layout +
                            " is already attached to a RecyclerView: " + layout.mRecyclerView);
                
                mLayout.setRecyclerView(this);
                if (mIsAttached) 
                    mLayout.dispatchAttachedToWindow(this);
                
            
            requestLayout();
        

    我们看这句代码:mLayout.setRecyclerView(this); 继续追踪下去:

/**
             * These measure specs might be the measure specs that were passed into RecyclerView's
             * onMeasure method OR fake measure specs created by the RecyclerView.
             * For example, when a layout is run, RecyclerView always sets these specs to be
             * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
             */
            private int mWidthSpec, mHeightSpec;

            void setRecyclerView(RecyclerView recyclerView) 
                if (recyclerView == null) 
                    mRecyclerView = null;
                    mChildHelper = null;
                    mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
                    mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
                 else 
                    mRecyclerView = recyclerView;
                    mChildHelper = recyclerView.mChildHelper;
                    mWidthSpec = MeasureSpec
                            .makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY);
                    mHeightSpec = MeasureSpec
                            .makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY);
                
            

    也就是测量RecyclerView 中itemView高度和宽度,然后对其绘制,可以实现水平滚动、垂直滚动、方块表格等列表形式。具体可以看LayoutManager这个类。
接下来我们看下这句代码:

recyclerView.setAdapter(new MyAdapter(this,datas));

    可知,它是为recyclerView存放数据集的,其中MyAdapter是我继承RecyclerView.Adapter

/**
         * Base class for an Adapter
         *
         * <p>Adapters provide a binding from an app-specific data set to views that are displayed
         * within a @link RecyclerView.</p>
         */
        public static abstract class Adapter<VH extends ViewHolder> 
            private final AdapterDataObservable mObservable = new AdapterDataObservable();
            private boolean mHasStableIds = false;

            /**
             * Called when RecyclerView needs a new @link ViewHolder of the given type to represent
             * an item.
             * <p>
             * This new ViewHolder should be constructed with a new View that can represent the items
             * of the given type. You can either create a new View manually or inflate it from an XML
             * layout file.
             * <p>
             * The new ViewHolder will be used to display items of the adapter using
             * @link #onBindViewHolder(ViewHolder, int, List). Since it will be re-used to display
             * different items in the data set, it is a good idea to cache references to sub views of
             * the View to avoid unnecessary @link View#findViewById(int) calls.
             *
             * @param parent The ViewGroup into which the new View will be added after it is bound to
             *               an adapter position.
             * @param viewType The view type of the new View.
             *
             * @return A new ViewHolder that holds a View of the given view type.
             * @see #getItemViewType(int)
             * @see #onBindViewHolder(ViewHolder, int)
             */
            public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);


            public abstract void onBindViewHolder(VH holder, int position);

             public void onBindViewHolder(VH holder, int position, List<Object> payloads) 
                onBindViewHolder(holder, position);
            

            /**
             * This method calls @link #onCreateViewHolder(ViewGroup, int) to create a new
             * @link ViewHolder and initializes some private fields to be used by RecyclerView.
             *
             * @see #onCreateViewHolder(ViewGroup, int)
             */
            public final VH createViewHolder(ViewGroup parent, int viewType) 
                TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
                final VH holder = onCreateViewHolder(parent, viewType);
                holder.mItemViewType = viewType;
                TraceCompat.endSection();
                return holder;
            
        ......

    我们可以重写该类的onBindViewHolder方法,进行自己的数据处理,而在onCreateViewHolder方法中我们可以读取item文件进行设置。相比ListView的优点就是RecyclerView直接将ViewHolder内置:
ViewHolder 部分代码:

 /**
         * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
         *
         * <p>@link Adapter implementations should subclass ViewHolder and add fields for caching
         * potentially expensive @link View#findViewById(int) results.</p>
         *
         * <p>While @link LayoutParams belong to the @link LayoutManager,
         * @link ViewHolder ViewHolders belong to the adapter. Adapters should feel free to use
         * their own custom ViewHolder implementations to store data that makes binding view contents
         * easier. Implementations should assume that individual item views will hold strong references
         * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
         * strong references to extra off-screen item views for caching purposes</p>
         */
        public static abstract class ViewHolder 
            public final View itemView;
            int mPosition = NO_POSITION;
            int mOldPosition = NO_POSITION;
            long mItemId = NO_ID;
            int mItemViewType = INVALID_TYPE;
            int mPreLayoutPosition = NO_POSITION;

            // The item that this holder is shadowing during an item change event/animation
            ViewHolder mShadowedHolder = null;
            // The item that is shadowing this holder during an item change event/animation
            ViewHolder mShadowingHolder = null;
            .......

    提供了很多属性。

ItemDecoration

/**
         * An ItemDecoration allows the application to add a special drawing and layout offset
         * to specific item views from the adapter's data set. This can be useful for drawing dividers
         * between items, highlights, visual grouping boundaries and more.
         *
         * <p>All ItemDecorations are drawn in the order they were added, before the item
         * views (in @link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()
         * and after the items (in @link ItemDecoration#onDrawOver(Canvas, RecyclerView,
         * RecyclerView.State).</p>
         */
        public static abstract class ItemDecoration 
            /**
             * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
             * Any content drawn by this method will be drawn before the item views are drawn,
             * and will thus appear underneath the views.
             *
             * @param c Canvas to draw into
             * @param parent RecyclerView this ItemDecoration is drawing into
             * @param state The current state of RecyclerView
             */
            public void onDraw(Canvas c, RecyclerView parent, State state) 
                onDraw(c, parent);
            

            /**
             * @deprecated
             * Override @link #onDraw(Canvas, RecyclerView, RecyclerView.State)
             */
            @Deprecated
            public void onDraw(Canvas c, RecyclerView parent) 
            

            /**
             * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
             * Any content drawn by this method will be drawn after the item views are drawn
             * and will thus appear over the views.
             *
             * @param c Canvas to draw into
             * @param parent RecyclerView this ItemDecoration is drawing into
             * @param state The current state of RecyclerView.
             */
            public void onDrawOver(Canvas c, RecyclerView parent, State state) 
                onDrawOver(c, parent);
            

            /**
             * @deprecated
             * Override @link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)
             */
            @Deprecated
            public void onDrawOver(Canvas c, RecyclerView parent) 
            


            /**
             * @deprecated
             * Use @link #getItemOffsets(Rect, View, RecyclerView, State)
             */
            @Deprecated
            public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 
                outRect.set(0, 0, 0, 0);
            

            /**
             * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
             * the number of pixels that the item view should be inset by, similar to padding or margin.
             * The default implementation sets the bounds of outRect to 0 and returns.
             *
             * <p>
             * If this ItemDecoration does not affect the positioning of item views, it should set
             * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
             * before returning.
             *
             * <p>
             * If you need to access Adapter for additional data, you can call
             * @link RecyclerView#getChildAdapterPosition(View) to get the adapter position of the
             * View.
             *
             * @param outRect Rect to receive the output.
             * @param view    The child view to decorate
             * @param parent  RecyclerView this ItemDecoration is decorating
             * @param state   The current state of RecyclerView.
             */
            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 
                getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                        parent);
            
        

    像我们以前用ListView都是在ItemView中去写好布局,现在我们的ItemView和要装饰的UI是分离出来的,用ItemDecoration这个方法即可装饰ItemView,比如项之间的分割线、Margin、绘制颜色等。我们也可以自定义ItemDecoration引用自己绘制的UI进行使用,功能上更加强大。

以上是关于RecyclerView源码分析的主要内容,如果未能解决你的问题,请参考以下文章

AppCompatTextView自动缩放字体在RecyclerView中卡顿,自定义高效自动缩放TextView

RecyclerView源码解析 - 分割线

RecyclerView源码分析

RecyclerView源码分析

RecyclerView源码分析

Android RecyclerView工作原理分析(上)