RecyclerView的基本设计结构

RecyclerView作为Android开发中最常用的View之一。很多App的feed流都是使用RecyclerView来实现的。加深对于RecyclerView的掌握对于开发效率和开发质量都有很重要的意义。接下来我打算从源码
角度剖析RecyclerView的实现,加深对于RecycledView的了解。RecyclerView的源码实现还是很庞大的。本文就先来看一下RecyclerView的整体设计,了解其核心实现类的作用以及大致实现原理。

下面这张图是我截取的RecyclerView的Structure:

类的组成.png

本文着重看: ViewHolderAdapterAdapterDataObservableRecyclerViewDataObserverLayoutManager、、RecyclerRecyclerPool。 从而理解RecycledView的大致实现原理。

先用一张图大致描述他们之间的关系,这张图是adapter.notifyXX()RecyclerView的执行逻辑涉及到的一些类:

RecyclerView组成类之间的关系.png

ViewHolder

对于Adapter来说,一个ViewHolder就对应一个data。它也是Recycler缓存池的基本单元。

class ViewHolder {
    public final View itemView;
    int mPosition = NO_POSITION;
    int mItemViewType = INVALID_TYPE;
    int mFlags;
    ...
}

上面我列出了ViewHolder最重要的4个属性:

  • itemView : 会被当做child viewaddRecyclerView中。
  • mPosition : 标记当前的ViewHolderAdapter中所处的位置。
  • mItemViewType : 这个ViewHolderType,在ViewHolder保存到RecyclerPool时,主要靠这个类型来对ViewHolder做复用。
  • mFlags : 标记ViewHolder的状态,比如 FLAG_BOUND(显示在屏幕上)FLAG_INVALID(无效,想要使用必须rebound)FLAG_REMOVED(已被移除)等。

Adapter

它的工作是把dataView绑定,即上面说的一个data对应一个ViewHolder。主要负责ViewHolder的创建以及数据变化时通知RecycledView。比如下面这个Adapter:

class SimpleStringAdapter(val dataSource: List<String>, val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder.itemView is ViewHolderRenderProtocol) {
            (holder.itemView as ViewHolderRenderProtocol).render(dataSource[position], position)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = SimpleVH(SimpleStringView(context))

    override fun getItemCount() = dataSource.size

    override fun getItemViewType(position: Int) = 1

    override fun notifyDataSetChanged() {   //super的实现
        mObservable.notifyChanged();
    }  
}

即:

  1. 它引用着一个数据源集合dataSource
  2. getItemCount()用来告诉RecyclerView展示的总条目
  3. 它并不是直接映射data -> ViewHolder, 而是 data position -> data type -> viewholder。 所以对于ViewHolder来说,它知道的只是它的view type

AdapterDataObservable

Adapter是数据源的直接接触者,当数据源发生变化时,它需要通知给RecyclerView。这里使用的模式是观察者模式AdapterDataObservable是数据源变化时的被观察者。RecyclerViewDataObserver是观察者。
在开发中我们通常使用adapter.notifyXX()来刷新UI,实际上Adapter会调用AdapterDataObservablenotifyChanged():

    public void notifyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }

逻辑很简单,即通知Observer数据发生变化。

RecyclerViewDataObserver

它是RecycledView用来监听Adapter数据变化的观察者:

    public void onChanged() {
        mState.mStructureChanged = true; // RecycledView每一次UI的更新都会有一个State
        processDataSetCompletelyChanged(true);
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }

LayoutManager

它是RecyclerView的布局管理者,RecyclerViewonLayout时,会利用它来layoutChildren,它决定了RecyclerView中的子View的摆放规则。但不止如此, 它做的工作还有:

  1. 测量子View
  2. 对子View进行布局
  3. 对子View进行回收
  4. 子View动画的调度
  5. 负责RecyclerView滚动的实现
  6. ...

Recycler

对于LayoutManager来说,它是ViewHolder的提供者。对于RecyclerView来说,它是ViewHolder的管理者,是RecyclerView最核心的实现。下面这张图大致描述了它的组成:

Recycler的组成.png

scrap list

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
  • View Scrap状态

相信你在许多RecyclerViewcrash log中都看到过这个单词。它是指ViewRecyclerView布局期间进入分离状态的子视图。即它已经被deatach(标记为FLAG_TMP_DETACHED状态)了。这种View是可以被立即复用的。它在复用时,如果数据没有更新,是不需要调用onBindViewHolder方法的。如果数据更新了,那么需要重新调用onBindViewHolder

mAttachedScrapmChangedScrap中的View复用主要作用在adapter.notifyXXX时。这时候就会产生很多scrap状态的view。 也可以把它理解为一个ViewHolder的缓存。不过在从这里获取ViewHolder时完全是根据ViewHolderposition而不是item type。如果在notifyXX时data已经被移除掉你,那么其中对应的ViewHolder也会被移除掉。

mCacheViews

可以把它理解为RecyclerView的一级缓存。它的默认大小是3, 从中可以根据item type或者position来获取ViewHolder。可以通过RecyclerView.setItemViewCacheSize()来改变它的大小。

RecycledViewPool

它是一个可以被复用的ViewHolder缓存池。即可以给多个RecycledView来设置统一个RecycledViewPool。这个对于多tab feed流应用可能会有很显著的效果。它内部利用一个ScrapData来保存ViewHolder集合:

class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;   //最多缓存5个
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
}

SparseArray<ScrapData> mScrap = new SparseArray<>();  //RecycledViewPool 用来保存ViewHolder的容器

一个ScrapData对应一种typeViewHolder集合。看一下它的获取ViewHolder和保存ViewHolder的方法:

//存
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size())  return; //到最大极限就不能放了
    scrap.resetInternal();  //放到里面,这个view就相当于和原来的信息完全隔离了,只记得他的type,清除其相关状态
    scrapHeap.add(scrap);
}

//取
private ScrapData getScrapDataForType(int viewType) {
    ScrapData scrapData = mScrap.get(viewType);
    if (scrapData == null) {
        scrapData = new ScrapData();
        mScrap.put(viewType, scrapData);
    }
    return scrapData;
}

以上所述,是RecycledView最核心的组成部分(本文并没有描述动画的部分)。

下一篇文章会分析RecyclerView的刷新机制

欢迎关注我的Android进阶计划。看更多干货

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容