RecyclerView源码分析

1.宏观认识

RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,支持RecyclerView高效运行有六个类.

属性名称功能描述
Adapter为每一项Item创建视图
ViewHolder承载Item视图的子布局
LayoutManager负责Item视图的布局的显示管理
ItemDecoration给每一项Item视图添加子View,例如可以进行画分隔线之类
ItemAnimator负责处理数据添加或者删除时候的动画效果
Recycler负责RecyclerView中子View的回收与复用

Adapter:主要负责ViewHolder的创建以及数据变化时通知RecycledView进行视图的更新。
Adapter类中有一个数据源集合dataSource的引用
getItemCount()用来告诉RecyclerView展示的总条目
它并不是直接映射data -> ViewHolder, 而是 data position -> data type -> viewholder。 所以对于ViewHolder来说,它知道的只是它的view type
ViewHolder:
对于Adapter来说,一个ViewHolder就对应一个data。它也是Recycler缓存池的基本单元。
在这里插入图片描述
itemView : 会被当做child view来add到RecyclerView中。
mPosition : 标记当前的ViewHolder在Adapter中所处的位置。
mItemViewType : 这个ViewHolder的Type,在ViewHolder保存到RecyclerPool时,主要靠这个类型来对ViewHolder做复用。
mFlags : 标记ViewHolder的状态,用在刷新上,比如 FLAG_BOUND(显示在屏幕上)、FLAG_INVALID(无效,想要使用必须rebound)、FLAG_REMOVED(已被移除)等。

/****************************************************************/
RecyclerView的职责就是将Datas中的数据以一定的规则展示在它的上面,但本质上RecyclerView只是一个ViewGroup,它只认识View,不清楚Data数据的具体结构。因此,RecyclerView需要一个Adapter来与Datas进行交流。Adapter的工作就是将Data转换为RecyclerView认识的ViewHolder
在这里插入图片描述

图 1-1
此外,Recycler将自己作为View的 onMeasure()、onLayout()直接交给LayoutManager,onDraw()交给了ItemDecoration来绘制。为了高效的管理ViewHold,引入了Recycler对ViewHold进行缓存管理。
到了这里,有负责翻译数据的Adapter,有负责布局的LayoutManager,有负责管理View的Recycler,一切都很完美,但RecyclerView为了更加人性化,引入了ItemAnimator来添加和控制ViewHold添加和删除的动画效果。最终的关系图如1-2所示
在这里插入图片描述

图 1-2

2.绘制的源码解析

参考文章

2.1.OnMeasure :与布局流程相互结合在一起的

在这里插入图片描述

onMeasure方法的代码很长,但是总的就分为三种情况:
没有LayoutManager的情况
有LayoutManager并开启自动测量
有LaoutManager但没有开启自动测量

在 没有LayoutManager的情况下,就是执行了defaultOnMeasure方法,里面就是计算并设置了RecyclerView的长宽值,不处理子VIew了

void defaultOnMeasure(int widthSpec, int heightSpec) {
    // calling LayoutManager here is not pretty but that API is already public and it is better
    // than creating another method since this is internal.
    final int width = LayoutManager.chooseSize(widthSpec,
            getPaddingLeft() + getPaddingRight(),
            ViewCompat.getMinimumWidth(this));
    final int height = LayoutManager.chooseSize(heightSpec,
            getPaddingTop() + getPaddingBottom(),
            ViewCompat.getMinimumHeight(this));

    setMeasuredDimension(width, height);
}

一般都会设置LayoutManager。从源码中看出不论是否启用 mAutoMeasure 最终都会执行到 mLayout.onMeasure() 方法中,而这个 mLayout 就是一个 LayoutManager 对象。
常用的三个LayoutManager,在其构造函数中,均已经开启了自动测量,所以我们可以放心地为RecyclerView设置wrap_content。
AutoMeasure是先通过LayoutManager的onLayoutChildren方法来实现子view的测量和布局,然后在获取子view的尺寸和位置来确定RecyclerView的尺寸


自动测绘过程可以分为两部分:

第一部分:
首先执行LayoutManager的onMeasure方法。(defaultOnMeasure)先要有一个值(这个值在xml文件中直接设置的)
检查如果width和height都已经是精确值(xxdp),那么就不用再根据内容进行计算所需要的width和height,那么跳过之后的步骤。如果有其中任何一个值不是精确值(wrap_content),则进入到下面计算所需长宽的步骤。
第二部分:
开启布局流程(依次调用dispatchLayoutStep1,2)(在step2中调用子类的onLayoutChildren()),计算出所有Child的边界。
然后根据计算出的Child的边界计算出RecyclerView的所需width和height,并设置。
检查是否需要再次测量,如果需要则在此进行测量。
PS: State是在RecyclerView中定义的静态内部类,用来保存RecyclerView的状态,像目标滚动的位置,视图的焦点等.

2.2.OnLayout()

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

核心是dispatchLayout()方法。在dispatchLayout()中主要有3个步骤
RecyclerView的布局过程分为3步:dispatchLayoutStep1,dispatchLayoutStep2和dispatchLayoutStep3。在之前自动化Measure过程中我们为了得到Child的边界值,使用了dispatchLayoutStep1和dispatchLayoutStep2;
/********************************************************/
DispatchLayoutStep1():
记录RecyclerView刷新前ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算
DispatchLayoutStep2():
做出呈现在手机上的试图效果
具体过程:调用实现子类的onLayoutChildern()函数,计算所有子View的边界值,根据计算出的Child的边界计算出RecyclerView的所需width和height,并设置。

DispatchLayoutStep3():
保持当前子视图的动画信息,在必要时进行触发或者清除

/************************************************************/

onLayout总结

  1. RecyclerView并没有对内部的View进行布局。而是交给LayoutManager去做具体的布局操作,因此才会有LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager这样灵活的布局。而且我们可以通过实现onLayoutChildren,自定义LayoutManager。
  2. RecyclerView的布局过程中包含很多动画相关的处理。
  3. RecyclerView的数据改变的动画是在布局过程的第三步中统一触发的。并不是一调用notify之后立即触发。

2.3.OnDraw()

@Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

除了绘制自己以外,还多调了一个mItemDecorations 的 onDraw() 方法.我们知道onDraw调用之前,会先调用draw方法,RecyclerView重写了draw方法:也就是先用draw()绘制item,然后再调用mItemDecorations的onDraw()来加一层图片,还要调用getItemOffsets(),让item的view发生一定的偏移,让在ItemView下的图片可以显示出来
参考文章

3.缓存机制

共有四级缓存机制,分别是Scrap,cache,viewCacheExtension,RecyclerPool
Recycler类的字段

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 RecycledViewPool mRecyclerPool;

private ViewCacheExtension mViewCacheExtension;

屏幕内缓存 :指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 。(2个scrap字段中)
mChangedScrap表示数据已经改变的ViewHolder列表
mAttachedScrap未与RecyclerView分离的ViewHolder列表
/*************************************
在这里插入图片描述

RecycledViewPool是最底层的缓存机制,当Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache最早的ViewHolder移到RecyclerViewPool.
默认的缓存数量是5个.特点是,从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder。所有从RecycledViewPool取出来的时候需要回调onBindViewHolder ()方法。
mRecyclerPool会根据ViewType把ViewHolder分别存储在不同的集合中,每个集合最多缓存5个ViewHolder。

在这里插入图片描述

优点

是1.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;2. b.mRecyclerPool可以供多个RecyclerView共同使用

RecyclerView最多可以缓存N(屏幕最多可显示的item数)+ 2 (屏幕外的缓存) + 5*M (M代表M个ViewType,缓存池的缓存)。
在这里插入图片描述

4.缓存策略

Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建。

5.Recycler的刷新机制(局部刷新)

这里使用的模式是观察者模式。
根据上述的关系图,与数据相关的都是适配器的工作。Adapter是数据源的直接接触者,当数据源发生变化时,它需要通知给RecyclerView。涉及到数据变化的检测,AdapterDataObsetvable, RecyclerViewDataObserver.
AdapterDataObservable是数据源变化时的被观察者。RecyclerViewDataObserver是观察者。
在这里插入图片描述

在开发中我们通常使用adapter.notifyXX()来刷新UI,实际上Adapter会调用AdapterDataObservable的notifyChanged():通知Observer数据发生变化
在这里插入图片描述
在这里插入图片描述

可以看出,数据集的变化最终会调用requestLayout() 对recyclerView重新进行绘制,onMeasure(),onLayout(), onDraw().主要是调用onLayoutChildren()函数,计算新的itemView的边界值
调用notifyxxxxx()时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分)
调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次。

这个所有的子View是表示数据集里的所有data.

在这里插入图片描述
在这里插入图片描述

RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView

参考文档1
参考文档2

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值