RecyclerView

比ListView功能更强大,对开发者比较友好

RecyclerView 具有非常好的性能,其得益于其设计的缓存机制or回收复用机制

概述

高效:可以循环使用

RecyclerView 是 Android 中用于展示大量数据集合的组件,它是 ListView 的升级版

灵活地显示列表数据

RecyclerView :容器 显示所有的内容和交互
Adapter :配置有多少 Item 及 Item 的样子要显示什么内容
ViewHolder :只管理当前这一个Item (使用 ViewHolder 管理每一个Item)
每个 Adapter 都有管理自己的 ViewHolder

  1. 添加 RecyclerView 控件
  2. 创建 Adapter
  3. 创建 ViewHolder(每个 ViewHolder 都需要自己来建)
    • onCreateViewHolder 解析 Item 的布局资源
  4. 给外部提供一个设置我的数据源的方法 setData
    • getItemCount() 根据数据源设置 Item 数量
  5. 在 Fragment 中配置 RecyclerView
    • adapter
    • layoutManager
    • 配置数据源
    • 按页显示 PagerSnapHelper().attachToRecyclerView(recyclerView)
  6. 把数据配置到对应的 Item 上
    • onBindViewHolder
    • MyViewHolder 中的 bind() 方法
    • 在bind方法中可以添加点击事件

使用步骤:

1.添加 RecyclerView 控件

在创建RecyclerView对应的Adapter之前,要先创建 Item 对应的 Layout

2.创建 Adapter

新建一个类

创建 Adapter ,继承于 RecyclerView.Adapter
创建对应的 ViewHolder

创建 Adapter 管理 RecyclerView 显示的子视图(即管理所有的 Item),Adapter 里面又需要一个 ViewHolder
在 Adapter 里面创建一个类取名 MyViewHolder 继承于 RecyclerView.ViewHolder,并需要传入其管理的 View

也可以传入 binding (可以减少 findViewById 这个操作)
class MyViewHolder(private val binding:LayoutPictureltemBinding):RecyclerView.ViewHolder(binding.root)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建 ViewHolder ,持有一个 view 实现重复利用
//实际上重复利用的是 ViewHolder ,但是 ViewHolder 又管理着一个对应的 view,所以就重复利用了那个对应的 view
class QQAdapter:RecyclerView.Adapter<QQAdapter.MyViewHolder>(){

class MyViewHolder(view: View):RecyclerView.ViewHolder(view){
fun bind(model:Friend){
val iconImageView = itemView.findViewById<ImageView>(R.id.imageView)
val nameTextView = itemView.findViewById<TextView>(R.id.tv_name)
iconImageView.setImageResource(model.icon)
nameTextView.text = model.name
}
}

//---有三个方法必须要实现

}

(1)onCreatViewHolder()

用于创建一个 ViewHolder 实例,加载item界面的布局文件

(确定每一个 Item 的样式,长什么样
通过 context 来获取系统中的一些资源)

创建 Item 对应的 layout 资源进行布局(解析)

1
2
3
4
5
6
7
8
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder{
//解析 layout 资源
//自己创建一个 layoutInflater 布局解析器
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.layout_qq_item,parent,false)

return MyViewHolder(view)
}
1
2
3
4
5
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder{
val inflater = Layoutlnflater.from(parent.context)
val binding = LayoutColorltemBinding.inflate(inflater,parent, attachToParent: false)
return MyViewHolder(binding)
}

(2)getItemCount()

配置 Item 的个数(外部给数据源才能配置其个数)
返回列表大小,即返回项的数量

1
2
3
override fun getItemCount():Int{
return mFriends.size
}

(3)onBindViewHolder()

绑定数据,告诉每一个 ViewHolder 该显示什么内容
在填充数据
采用两个参数: ViewHolder ,以及要绑定数据的位置

1
2
3
4
5
override fun onBindViewHolder(holder: MyViewHolder, position: Int){
//取出 position 对应的数据源 拿给 ViewHolder 去显示

holder.bind(mFriends[position])
}
1
2
3
4
5
6
7
8
9
10
11
12
class MyViewHolder(view: View):RecyclerView.ViewHolder(view){
//把数据拿给每个控件去刷新
fun bind(model:Friend){
//找到对应的控件,把数据设置给它 itemView 就是 view

val iconImageView = itemView.findViewById<ImageView>(R.id.imageView)
val nameTextView = itemView.findViewById<TextView>(R.id.tv_name)

iconImageView.setImageResource(model.icon)
nameTextView.text = model.name
}
}

3.创建 ViewHolder(每个 ViewHolder 都需要自己来建)

创建 Item 对应的layout资源,进行布局
创建布局文件,解析布局文件,在 onCreatViewHolder 返回 MyViewHolder 传入该 view

1
2
3
4
5
6
7
8
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder{
//解析 layout 资源
//自己创建一个 layoutInflater 布局解析器
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.layout_qq_item,parent,false)

return MyViewHolder(view)
}
1
2
3
4
5
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder{
val inflater = Layoutlnflater.from(parent.context)
val binding = LayoutColorltemBinding.inflate(inflater,parent, attachToParent: false)
return MyViewHolder(binding)
}

4.在 Fragment 中配置 RecyclerView

在 Fragment 的 onViewCreated() 方法中

(1)先找到 recyclerView

val recyclerView = binding.recyclerView

(2)配置布局方式 LinearLayoutManager 布局管理器

layoutManager 布局管理器,就是 Item 的管理布局的(这个属性最重要)

系统提供的只有三种(没有想要的就自定义):
LinearLayoutManager(context,orientation,reverselayout) 线性
StaggeredGridLayoutManager(spanCount: Int orientation: int) 交错 错列网络(一行显示几个)
GridLayoutManager 网格

正常 交错式显示 瀑布流 网格

1
2
3
4
5
recyclerView.layoutManager = LinearLayoutManager(         
requireContext(), //context
RecyclerView.VERTICAL,//HORIZONTAL 横向或竖向滚动
false //是否需要反过来布局 123 or 321
)

(3)配置适配器 adapter

1
2
3
//给对应的数据 得知要显示什么东西
val mAdapter = QQAdapter()
recyclerView.adapter = mAdapter

(4)按页显示

这样可以达到 ViewPager 的效果

1
PagerSnapHelper().attachToRecyclerView(recyclerView)

(5)配置数据源

1
2
3
4
5
6
mAdapter.setData(listOf(
Friend(R.drawable.icon1,"Jack",""),
Friend(R.drawable.icon2,"Rose",""),
Friend(R.drawable.icon3,"Marry",""),
Friend(R.drawable.icon4,"Lily",""),
))}

5.把数据配置到对应的 Item 上

将数据显示到对应的 Item上
具体在 onBindViewHolder 、 MyViewHolder 中 bind 方法

1
2
3
4
5
override fun onBindViewHolder(holder: MyViewHolder, position: Int){
//取出 position 对应的数据源 拿给 ViewHolder 去显示

holder.bind(mFriends[position])
}

可以直接通过 itemView 得到我们传过去的 view
这里的 itemView 是 ViewHolder 中的一个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyViewHolder(view: View):RecyclerView.ViewHolder(view){
//把对应的数据设置给 view
//把数据拿给每个控件去刷新

fun bind(model:Friend){
//找到对应的控件(设置对应的属性值)把数据设置给它
//这里的 itemView 是 ViewHolder 中的一个属性,存储的是 view

val iconImageView = itemView.findViewById<ImageView>(R.id.imageView)
val nameTextView = itemView.findViewById<TextView>(R.id.tv_name)

iconImageView.setImageResource(model.icon)
nameTextView.text = model.name
}

}

常用方法

  1. setLayoutManager(layoutManager: RecyclerView.LayoutManager): 设置RecyclerView的布局管理器,用于确定列表项的排列方式,如线性布局、网格布局等。
  2. setAdapter(adapter: RecyclerView.Adapter): 设置RecyclerView的适配器,用于绑定数据和视图,以便在列表中显示数据项。
  3. addItemDecoration(decoration: RecyclerView.ItemDecoration): 添加列表项的装饰,如分割线、间距等。
  4. addItemTouchHelper(callback: ItemTouchHelper.Callback): 添加滑动删除、拖动排序等交互功能。
  5. scrollToPosition(position: Int): 滚动RecyclerView到指定位置。
  6. smoothScrollToPosition(position: Int): 平滑滚动RecyclerView到指定位置。
  7. addOnScrollListener(listener: RecyclerView.OnScrollListener): 添加滚动监听器,用于监听RecyclerView的滚动状态。
  8. setOnItemClickListener(listener: (position: Int) -> Unit): 设置列表项的点击事件监听器。

RecyclerView 的 setOnScrollChangeListener 方法用于设置滚动监听器,监听RecyclerView的滚动状态变化。

RecyclerView 还提供了一些其他类似的滚动监听方法,例如:

  1. addOnScrollListener(RecyclerView.OnScrollListener listener):添加一个滚动监听器,继承自RecyclerView.OnScrollListener类,可以通过重写其方法来监听RecyclerView的滚动状态变化。

  2. clearOnScrollListeners():清除所有已设置的滚动监听器。

  3. addOnItemTouchListener(RecyclerView.OnItemTouchListener listener):添加一个项目触摸监听器,继承自RecyclerView.OnItemTouchListener类,可以通过重写其方法来监听RecyclerView中项目的触摸事件。

这些方法可以帮助开发者更灵活地监听RecyclerView的滚动和触摸事件,实现各种交互效果和功能。

刷新问题:
notifyDataSetChanged()//全部刷新
notifyltemChanged( position: 1)//某一个
notifyltemRangeChanged( positionStart: 0, itemCount: 3)//从某个开始刷新几个

得到当前页

findFirstCompletelyVisibleItemPosition

1
2
3
//得到当前页的index
val lm = binding.recyclerView.layoutManager as LinearLayoutManager
val index = lm.findFirstCompletelyVisibleItemPosition()

常用属性

  1. layoutManager: 用于设置 RecyclerView 的布局管理器,确定列表项的排列方式,如线性布局、网格布局等。
  2. adapter: 用于设置 RecyclerView 的适配器,绑定数据和视图,以便在列表中显示数据项。
  3. itemDecoration: 用于添加列表项的装饰,如分割线、间距等。
  4. itemAnimator: 用于设置列表项的动画效果,如添加、删除、移动等操作时的动画效果。
  5. scrollState: 用于获取 RecyclerView 的滚动状态,如SCROLL_STATE_IDLE(空闲状态)、SCROLL_STATE_DRAGGING(拖动状态)、SCROLL_STATE_SETTLING(自动滚动状态)。
  6. onScrollListener: 用于添加滚动监听器,监听 RecyclerView 的滚动事件。
  7. onItemClickListener: 用于设置列表项的点击事件监听器。

原理

四级缓存

  1. mChangeScrap与 mAttachedScrap用来缓存还在屏幕内的 ViewHolder

  2. mCachedViews 用来缓存移除屏幕之外的 ViewHolder

  3. mViewCacheExtension 这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据(自定义的)

  4. RecycledViewPool ViewHolder 缓存池

    缓存和复用到底处理的是什么?

    复用的是ViewHolder —- ItemView (ViewHolder就是View的容器)

缓存什么?复用什么?
缓存到哪里去?从哪里获得复用?
什么时候缓存?什么时候复用?

看源码的入口:

  • 滑动入口 OnTouchEvent
  • 布局入口 OnLayout

滑动入口

我们在滑动屏幕的时候触发缓存机制,

直接找到OnTouchEvent()方法

里面有scrollByInternal

真正的滚动机制是在scrollBy
fill() 关键
fill()填充给定的布局,里面有个while循环,就是通过while循环不断去填充
循环里面有layoutChunk()块

layoutChunk()里面使用addView添加的
如何拿到View的:View view = layoutState.next(recycler);

next() getViewForPosition()

tryGetViewHolderForPositionByDeadline() //又回到RecyclerView中了

关键代码,复用的话基本就在这个里面

复用有几级就要看其缓存有几级

//前两层,根据position或者id获得

holder = getChangedScrapViewForPosition(position);

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);

//第三层,自定义的

final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);

//第四层,从缓存池中获取

holder = getRecycledViewPool().getRecycledView(type);

//若是四层都有没就去创建

holder = mAdapter.createViewHolder(RecyclerView.this, type);

//抽象方法,

public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

在复用不成功的情况下才会创建,调用onCreatViewHolder

创建了ViewHolder之后,ViewHolder只是个容器,需要数据

bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);

mAdapter.bindViewHolder(holder, offsetPosition);

onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());

BindViewHolder什么时候去处理呢?其实只要我们数据改变,上下滑动,肯定会执行


先从界面拿,拿不到去mCachedViews拿,也拿不到找RecyclerViewPool拿,那不到就去创建

布局入口

下一个路口 : 布局

在RecyclerView中找onLayout()

dispatchLayout()

分成了三块

dispatchLayoutStep1();
dispatchLayoutStep2();
dispatchLayoutStep3();

2里
mLayout.onLayoutChildren(mRecycler, mState);
后面就接上fill()了//复用

在fill的上面(在onLayoutChildren()里)

detachAndScrapAttachedViews(recycler);//缓存

scrapOrRecycleView(recycler, i, v);

布局是交给LayoutManager来处理的,内部的缓存复用是交给Recycler这个类处理的

两个处理的方式

(1)recycler.recycleViewHolderInternal(viewHolder);

这里主要是做第二和第四级缓存的

  1. mCachedViews 用来缓存移除屏幕之外的 ViewHolder

  2. RecycledViewPool ViewHolder 缓存池

第1种:mCachedViews满了的话就放到缓存池中

会走if里面
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

1
2
3
4
5
6
7
int cachedViewSize = mCachedViews.size();//其默认大小是2
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}

mCachedViews.add(targetCacheIndex, holder);//加新的

recycleCachedViewAt(0);

1
2
3
4
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);//拿到
addViewHolderToRecycledViewPool(viewHolder, true);//放到缓存池
mCachedViews.remove(cachedViewIndex);//移除

它如果满了,会将第0位的ViewHolder拿出来,放到RecyclerViewPool缓存池中,再把它remove干掉,每次都是把第0个干掉,新的放在后面


第2种:直接添加到缓存池中

1
2
3
4
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}

addViewHolderToRecycledViewPool(holder, true);

最后一行:getRecycledViewPool().putRecycledView(holder);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
//这里根ViewHolder的类型找
//拿到对应类型的集合
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;

//缓存池满时的操作
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}

//没满,清空和添加操作
scrap.resetInternal();
scrapHeap.add(scrap);
}

getScrapDataForType()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}

//----------
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; //最大是5个
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}

RecycledViewPoll每种类型对应5个大小

putRecycledView()方法中,如果缓存池满了,想再放就直接不要


mCachedViews缓存的是带有数据的ViewHolder,但是其容量默认只有2,但是为什么不扩大?缓存的数据越多,这个滑动的效率肯定是越高的,可能考虑性能方面的

RecycledView缓存池

当轻微滑动屏幕时,靠近的两个缓存在mCachedViews里面的是可以直接拿来使用的,不需要onBindViewHolder,然而大面积滑动是需要的,这个时候就是从缓存池里面拿的

(2)recycler.scrapView(view);

1.mChangeScrap与 mAttachedScrap用来缓存还在屏幕内的 ViewHolder(这看的就是第一层缓存了)

处理在屏幕内有去改变的,比如有一些会有动画的处理,用mAttachedScrap储存,其他的普通情况用mChangedScrap储存

面试

RecyclerView的循环重复利用机制

谈谈RecyclerView的缓存机制?(缓存复用机制)

RecyclerView 具有非常好的性能,其得益于其设计的缓存机制or回收复用机制

1. 缓存机制的核心思想

RecyclerView的缓存机制是为了在滚动时高效地复用ItemView,避免频繁创建和销毁View,从而提升性能。RecyclerView通过多级缓存来管理ItemView的复用。

2. 四级缓存结构

RecyclerView的缓存机制主要分为四级:

  • RecyclerView 是 Android 开发中用于高效展示大量数据的控件,其核心优势之一就是通过四级缓存机制来实现 ViewHolder 的复用,从而提升性能。以下是 RecyclerView 的四级缓存机制及其工作原理:


(1)mAttachedScrap 和 mChangeScrap

  • 作用:这两个缓存用于存储当前仍在屏幕内的 ViewHolder。
  • 区别
    • mAttachedScrap:存储与 RecyclerView 仍然关联的 ViewHolder(例如在布局过程中临时移除的 ViewHolder)。
    • mChangeScrap:存储由于数据变化(如 notifyItemChanged)而暂时移除的 ViewHolder。
  • 特点
    • 这些 ViewHolder 仍然与 RecyclerView 绑定,可以直接复用。
    • 不需要重新绑定数据(onBindViewHolder),因为数据未发生变化。
  • 使用场景:在布局过程中,RecyclerView 会优先从这两个缓存中获取 ViewHolder,避免不必要的创建和绑定操作。

(2)mCachedViews

  • 作用:用于缓存刚刚滑出屏幕的 ViewHolder。
  • 特点
    • 默认大小为 2(可以通过 setItemViewCacheSize 调整)。
    • 存储的 ViewHolder 是完整的,包括数据和视图状态。
    • 当用户快速滑动时,RecyclerView 会优先从 mCachedViews 中获取 ViewHolder,避免重新绑定数据。
  • 使用场景:适用于快速滚动的场景,例如用户快速滑动列表时,刚刚滑出的 ViewHolder 可以快速复用。

(3)mViewCacheExtension

  • 作用:这是一个开发者自定义的缓存层,系统不会自动管理它。
  • 特点
    • 完全由开发者控制,可以根据业务需求实现特定的缓存逻辑。
    • 需要继承 ViewCacheExtension 并实现相关方法。
  • 使用场景:适用于需要特殊缓存逻辑的场景,例如缓存特定类型的 ViewHolder 或对缓存进行更精细的控制。

(4) RecycledViewPool

  • 作用:ViewHolder 的缓存池,用于存储不同类型的 ViewHolder。
  • 特点
    • 多个 RecyclerView 可以共享同一个 RecycledViewPool
    • 存储的 ViewHolder 是解耦的,即数据会被清除(需要重新调用 onBindViewHolder)。
    • 每个类型的 ViewHolder 默认缓存大小为 5(可以通过 setMaxRecycledViews 调整)。
  • 使用场景:适用于需要复用 ViewHolder 但数据需要重新绑定的场景,例如多个 RecyclerView 共享 ViewHolder 的情况。

缓存复用机制的工作流程

  1. 布局阶段

    • RecyclerView 会优先从 mAttachedScrapmChangeScrap 中获取 ViewHolder。
    • 如果未找到,则从 mCachedViews 中查找。
    • 如果仍未找到,则从 RecycledViewPool 中获取。
    • 如果所有缓存都未命中,则创建新的 ViewHolder。
  2. 滑动阶段

    • 当 ViewHolder 滑出屏幕时,会优先存入 mCachedViews
    • 如果 mCachedViews 已满,则最旧的 ViewHolder 会被转移到 RecycledViewPool
  3. 数据更新阶段

    • 当数据发生变化时,mChangeScrap 会临时存储受影响的 ViewHolder。
    • 更新完成后,这些 ViewHolder 会重新绑定数据并显示。

总结

RecyclerView 的四级缓存机制通过分层管理 ViewHolder,最大限度地减少了 ViewHolder 的创建和绑定操作,从而提升了性能。开发者可以根据实际需求调整缓存策略,例如增加 mCachedViews 的大小或自定义 mViewCacheExtension,以进一步优化 RecyclerView 的表现。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信