RecyclerView源码分析

Posted tezlikai

tags:

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

我先从普通的 AdapterViewRecyclerView的比较说起,后面再详细介绍几个关键类。

AdapterView vs. RecyclerView

  • Item复用方面RecyclerView内置了RecyclerViewPool、多级缓存、ViewHolder,而AdapterView需要       手动添加ViewHolder且复用功能也没RecyclerView更加完善

  • 样式丰富方面RecyclerView通过支持水平、垂直和表格列表及其他更复杂形式,而AdapterView只支持具体某一种

  • 效果增强方面RecyclerView内置了ItemDecorationItemAnimator,可以自定义绘制itemView之间的一些特殊UI或item项数据变化时的动画效果,而用AdapterView实现时采取的做法是将这些特殊UI作为itemView的一部分,设置可见不可见决定是否展现,且数据变化时的动画效果没有提供,实现起来比较麻烦

  • 代码内聚方面RecyclerView将功能密切相关的类写成内部类,如ViewHolderAdapter,而AdapterView没有

1. Recycler

(1)Recycler简介

Recycler用于管理已经废弃或与RecyclerView分离的(scrapped or detached)item view,便于重用。

Scrapped view指依附于RecyclerView,但被标记为可移除或可复用的view。

LayoutManager获取Adapter某一项的View时会使用Recycler。当复用的View有效(clean)时,View能直接被复用,反之若View失效(dirty)时,需要重新绑定View。对于有效的View,如果不主动调用request layout,则不需要重新测量大小就能复用

(2)原理解析

在分析Recycler的复用原理之前,我们先了解下如下两个类:

RecycledViewPool

RecyclerViewPool用于多个RecyclerView之间共享View。只需要创建一个RecyclerViewPool实例,然后调用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默认会创建一个RecyclerViewPool实例。

 
  1.     public static class RecycledViewPool 
  2.     private SparseArray<ArrayList<ViewHolder>> mScrap =
  3.             new SparseArray<ArrayList<ViewHolder>>();
  4.     private SparseIntArray mMaxScrap = new SparseIntArray();
  5.     private int mAttachCount = 0;
  6.  
  7.     private static final int DEFAULT_MAX_SCRAP = 5;
  8.  
  9.     public void clear() 
  10.         mScrap.clear();
  11.     
  12.  
  13.     public void setMaxRecycledViews(int viewType, int max) 
  14.         mMaxScrap.put(viewType, max);
  15.         final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
  16.         if (scrapHeap != null) 
  17.             while (scrapHeap.size() > max) 
  18.                 scrapHeap.remove(scrapHeap.size() - 1);
  19.             
  20.         
  21.     
  22.  
  23.     public ViewHolder getRecycledView(int viewType) 
  24.         final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
  25.         if (scrapHeap != null && !scrapHeap.isEmpty()) 
  26.             final int index = scrapHeap.size() - 1;
  27.             final ViewHolder scrap = scrapHeap.get(index);
  28.             scrapHeap.remove(index);
  29.             return scrap;
  30.         
  31.         return null;
  32.     
  33.  
  34.     int size() 
  35.         int count = 0;
  36.         for (int i = 0; i < mScrap.size(); i ++) 
  37.             ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
  38.             if (viewHolders != null) 
  39.                 count += viewHolders.size();
  40.             
  41.         
  42.         return count;
  43.     
  44.  
  45.     public void putRecycledView(ViewHolder scrap) 
  46.         final int viewType = scrap.getItemViewType();
  47.         final ArrayList scrapHeap = getScrapHeapForType(viewType);
  48.         if (mMaxScrap.get(viewType) <= scrapHeap.size()) 
  49.             return;
  50.         
  51.         if (DEBUG && scrapHeap.contains(scrap)) 
  52.             throw new IllegalArgumentException("this scrap item already exists");
  53.         
  54.         scrap.resetInternal();
  55.         scrapHeap.add(scrap);
  56.     
  57.  
  58.     void attach(Adapter adapter) 
  59.         mAttachCount++;
  60.     
  61.  
  62.     void detach() 
  63.         mAttachCount--;
  64.     
  65.  
  66.  
  67.     /**
  68.      * Detaches the old adapter and attaches the new one.
  69.      * <p>
  70.      * RecycledViewPool will clear its cache if it has only one adapter attached and the new
  71.      * adapter uses a different ViewHolder than the oldAdapter.
  72.      *
  73.      * @param oldAdapter The previous adapter instance. Will be detached.
  74.      * @param newAdapter The new adapter instance. Will be attached.
  75.      * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
  76.      *                               ViewHolder and view types.
  77.      */
  78.     void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
  79.             boolean compatibleWithPrevious) 
  80.         if (oldAdapter != null) 
  81.             detach();
  82.         
  83.         if (!compatibleWithPrevious && mAttachCount == 0) 
  84.             clear();
  85.         
  86.         if (newAdapter != null) 
  87.             attach(newAdapter);
  88.         
  89.     
  90.  
  91.     private ArrayList<ViewHolder> getScrapHeapForType(int viewType) 
  92.         ArrayList<ViewHolder> scrap = mScrap.get(viewType);
  93.         if (scrap == null) 
  94.             scrap = new ArrayList<ViewHolder>();
  95.             mScrap.put(viewType, scrap);
  96.             if (mMaxScrap.indexOfKey(viewType) < 0) 
  97.                 mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
  98.             
  99.         
  100.         return scrap;
  101.     

通过源码我们可以看出mScrap是一个<viewType, List>的映射,`mMaxScrap`是一个<viewType, maxNum>的映射,这两个成员变量代表可复用View池的基本信息。调用`setMaxRecycledViews(int viewType, int max)`时,当用于复用的`mScrap`中viewType对应的ViewHolder个数超过maxNum时,会从列表末尾开始丢弃超过的部分。调用`getRecycledView(int viewType)`方法时从`mScrap`中移除并返回viewType对应的List的末尾项。

ViewCacheExtension

ViewCacheExtension是一个由开发者控制的可以作为View缓存的帮助类。调用Recycler.getViewForPosition(int)方法获取View时,Recycler先检查attached scrap和一级缓存,如果没有则检查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果没有则检查RecyclerViewPool。注意:Recycler不会在这个类中做缓存View的操作,是否缓存View完全由开发者控制。

 
  1. public abstract static class ViewCacheExtension 
  2.     abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);

现在大家熟悉了RecyclerViewPoolViewCacheExtension的作用后,下面开始介绍Recycler。如下是Recycler的几个关键成员变量和方法:

private ArrayList<ViewHolder> mAttachedScrap  
private ArrayList<ViewHolder> mChangedScrap与RecyclerView分离的ViewHolder列表。

private ArrayList<ViewHolder> mCachedViewsViewHolder缓存列表。

private ViewCacheExtension mViewCacheExtension开发者控制的ViewHolder缓存

private RecycledViewPool mRecyclerPool提供复用ViewHolder池。

public void bindViewToPosition(View view, int position)  
将某个View绑定到Adapter的某个位置。

public View getViewForPosition(int position)

获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:  
(1)检查mChangedScrap,若匹配到则返回相应holder  
(2)检查mAttachedScrap,若匹配到且holder有效则返回相应holder  
(3)检查mViewCacheExtension,若匹配到则返回相应holder  
(4)检查mRecyclerPool,若匹配到则返回相应holder  
(5)否则执行Adapter.createViewHolder(),新建holder实例  
(6)返回holder.itemView  
(7)注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)

2. LayoutManager

LayoutManager主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。通过设置Layout Manager的属性,可以实现水平滚动、垂直滚动、方块表格等列表形式。其内部类Properties包含了所需要的大部分属性

3. ViewHolder

对于传统的AdapterView,需要在实现的Adapter类中手动加ViewHolderRecyclerView直接将ViewHolder内置,并在原来基础上功能上更强大。ViewHolder描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实现类通常用于保存findViewById的结果。主要元素组成有:

 
  1. public static abstract class ViewHolder 
  2.     View itemView;//itemView
  3.     int mPosition;//位置
  4.     int mOldPosition;//上一次的位置
  5.     long mItemId;//itemId
  6.     int mItemViewType;//itemViewType
  7.     int mPreLayoutPosition;
  8.     int mFlags;//ViewHolder的状态标志
  9.     int mIsRecyclableCount;
  10.     Recycler mScrapContainer;//若非空,表明当前ViewHolder对应的itemView可以复用

关于ViewHolder,我最想介绍的是mFlags。  
FLAG_BOUND——ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效  
FLAG_UPDATE——ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的  
FLAG_INVALID——ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据  
FLAG_REMOVED——ViewHolder对应的数据已经从数据集移除  
FLAG_NOT_RECYCLABLE——ViewHolder不能复用  
FLAG_RETURNED_FROM_SCRAP——这个状态的ViewHolder会加到scrap list被复用。  
FLAG_CHANGED——ViewHolder内容发生变化,通常用于表明有ItemAnimator动画  
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能复用  
FLAG_TMP_DETACHED——ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来  
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置  
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)调用时设置

4. Adapter

AdapterView中用到的BaseAdapterListAdapter等作用类似,都是作为itemView和data之间的适配器,将data绑定到某一个itemView上。差别在于,RecyclerViewAdapter内置作为其内部类,我认为将功能密切相关的类以内部类的形式定义使得代码内聚更好,更便于理解与阅读。

5. ItemDecoration

当我们想在某些item上加一些特殊的UI时,往往都是在itemView中先布局好,然后通过设置可见性来决定哪些位置显示不显示。RecyclerView将itemView和装饰UI分隔开来,装饰UI即ItemDecoration,主要用于绘制item间的分割线、高亮或者margin等。其源码如下:

 
  1. public static abstract class ItemDecoration 
  2.     //itemView绘制之前绘制,通常这部分UI会被itemView盖住
  3.     public void onDraw(Canvas c, RecyclerView parent, State state) 
  4.         onDraw(c, parent);
  5.     
  6.  
  7.     //itemView绘制之后绘制,这部分UI盖在itemView上面
  8.     public void onDrawOver(Canvas c, RecyclerView parent, State state) 
  9.         onDrawOver(c, parent);
  10.     
  11.  
  12.     //设置itemView上下左右的间距
  13.     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 
  14.         getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
  15.                 parent);
  16.     

6. ItemAnimator

过去AdapterView的item项操作往往是没有动画的。现在RecyclerViewItemAnimator使得item的动画实现变得简单而样式丰富,我们可以自定义item项不同操作(如添加,删除)的动画效果。

7. 触摸事件监听

关于Item项的手势监听事件,如单击和双击没有像其他AdapterView一样分别提供具体接口,但是RecyclerView提供OnItemTouchListener接口和SimpleOnItemTouchListener实现类,大家可以通过继承去实现自己想要的单击双击或其他事件监听。

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

RecyclerView源码分析

Android 事件分发ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )

如何在内部类中定义常量?

【源码解析】RecyclerView的工作原理

没有滚动时屏幕底部的 Recyclerview 页脚和滚动时位于列表末尾的 Recyclerview 页脚

如何在 Android 中的 RecyclerView 末尾添加一个加号图像作为按钮? [复制]