查看原文
其他

图文搞懂RecyclerView 的各部分组成 | RecyclerView进阶

susion哒哒 鸿洋 2019-04-05

本文作者


作者:susion哒哒

链接:

https://www.jianshu.com/p/a57608f2695f

本文由作者授权发布。


这是一个系列,该文为系列第一篇。


1概述


RecyclerView作为Android开发中最常用的View之一。很多App的feed流都是使用RecyclerView来实现的。加深对于RecyclerView的掌握对于开发效率和开发质量都有很重要的意义。


接下来我打算从源码角度剖析RecyclerView的实现,加深对于RecycledView的了解。RecyclerView的源码实现还是很庞大的。本文就先来看一下RecyclerView的整体设计,了解其核心实现类的作用以及大致实现原理。


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


类的组成.png


本文着重看: 


ViewHolder、Adapter、AdapterDataObservable、RecyclerViewDataObserver、LayoutManager、、Recycler、RecyclerPool。 


从而理解RecycledView的大致实现原理。


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

RecyclerView组成类之间的关系.png


2ViewHolder


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


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


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


  1. itemView : 会被当做child view来add到RecyclerView中。

  2. mPosition : 标记当前的ViewHolder在Adapter中所处的位置。

  3. mItemViewType : 这个ViewHolder的Type,在ViewHolder保存到RecyclerPool时,主要靠这个类型来对ViewHolder做复用。

  4. mFlags : 标记ViewHolder的状态,比如 FLAG_BOUND(显示在屏幕上)、FLAG_INVALID(无效,想要使用必须rebound)、FLAG_REMOVED(已被移除)等。


3Adapter


它的工作是把data和View绑定,即上面说的一个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


4AdapterDataObservable


Adapter是数据源的直接接触者,当数据源发生变化时,它需要通知给RecyclerView。这里使用的模式是观察者模式。AdapterDataObservable是数据源变化时的被观察者。RecyclerViewDataObserver是观察者。


在开发中我们通常使用adapter.notifyXX()来刷新UI,实际上Adapter会调用AdapterDataObservable的notifyChanged():


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


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


5RecyclerViewDataObserver


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


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


6LayoutManager


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


  1. 测量子View

  2. 对子View进行布局

  3. 对子View进行回收

  4. 子View动画的调度

  5. 负责RecyclerView滚动的实现

  6. ...


7Recycler



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

Recycler的组成.png


scrap list


final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;


View Scrap状态


相信你在许多RecyclerView的crash log中都看到过这个单词。它是指View在RecyclerView布局期间进入分离状态的子视图。即它已经被deatach(标记为FLAG_TMP_DETACHED状态)了。这种View是可以被立即复用的。


它在复用时,如果数据没有更新,是不需要调用onBindViewHolder方法的。如果数据更新了,那么需要重新调用onBindViewHolder。


mAttachedScrap和mChangedScrap中的View复用主要作用在adapter.notifyXXX时。这时候就会产生很多scrap状态的view。 也可以把它理解为一个ViewHolder的缓存。不过在从这里获取ViewHolder时完全是根据ViewHolder的position而不是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对应一种type的ViewHolder集合。看一下它的获取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组件化

Android 如何优雅地实现@人功能?

同学,你的系统Toast可能需要修复一下




扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存