RecyclerView 相比 ListView
- 性能更优
RecyclerView 通过更有效的视图回收机制减少了内存使用,提升了性能。RecyclerView 仅在需要时创建新的视图,并在滚动时回收和重用旧的视图,从而减少了内存分配和垃圾回收的开销。
- 各种布局管理器灵活性更高
RecyclerView 提供了高度可定制的布局管理器(LayoutManager),可以轻松实现各种布局效果,如线性布局、网格布局、瀑布流布局等。也可以通过自定义 LayoutManager 实现更复杂的布局需求。
- 更强的可扩展性
RecyclerView 提供了灵活的适配器模式(Adapter)和 ViewHolder 模式,允许开发者更高效地绑定数据和视图
- 内置动画支持
RecyclerView 提供了内置的动画 ItemAnimator 支持,如插入、删除和移动项目时的动画效果
- 分割线和装饰物
RecyclerView 允许通过 ItemDecoration 类来自定义分割线和装饰物
- 事件处理
RecyclerView 的事件处理机制更加灵活。开发者可以通过设置 RecyclerView.OnItemTouchListener 接口来处理复杂的触摸事件,而 ListView 只能处理简单的点击和长按事件。
- 数据变化的通知
RecyclerView 的 Adapter 提供了一套完善的通知数据变化的方法,如 notifyItemInserted、notifyItemRemoved、notifyItemChanged 等,能够精细控制每个项目的变化,避免刷新整个列表,提高性能。
三层缓存
RecyclerView 的三层缓存策略包括 View Cache、Recycler Pool 和 Scrap Heap。以下是这三层缓存的具体使用流程:
1. View Cache
View Cache 是 RecyclerView 内部的一个缓存,用于保存刚刚被移出屏幕的 ViewHolder。其主要作用是快速复用这些 ViewHolder,而不需要重新创建。
使用流程:
- 移出屏幕: 当一个 ViewHolder 移出屏幕时,它首先被放入 View Cache。
- 复用: 当需要一个新的 ViewHolder 时,RecyclerView 首先检查 View Cache。如果有可用的 ViewHolder,就直接复用。
- 缓存大小: View Cache 的大小通常有限制(默认是 2 或 5),超过这个限制的 ViewHolder 会被移入 Recycler Pool。
2. Recycler Pool
RecyclerView 有一个内部的 Recycler,它维护了一个回收池,用于存储离开屏幕但仍可重用的 ViewHolder。Recycler Pool 是一个更通用的缓存池,用于保存各种类型的 ViewHolder。它可以跨多个 RecyclerView 共享,用于更大范围的 ViewHolder 复用。
使用流程:
- View Cache 满: 当 View Cache 满了,多余的 ViewHolder 会被移入 Recycler Pool。
- 复用: 当 View Cache 没有可用的 ViewHolder 时,RecyclerView 会检查 Recycler Pool。如果有合适的 ViewHolder,就从中取出并复用。
- 自定义: 开发者可以自定义 Recycler Pool 的行为,例如设置每种 ViewHolder 类型的缓存数量。
3. Scrap Heap
Scrap Heap 是一个临时缓存,用于保存当前屏幕上不可见但仍在屏幕范围内的 ViewHolder。通常用于处理滚动过程中暂时不可见的视图。
使用流程:
- 临时不可见: 当视图在屏幕范围内但暂时不可见时(例如被覆盖),RecyclerView 会将其 ViewHolder 存入 Scrap Heap。
- 复用: 当这些视图重新变为可见时,RecyclerView 会首先检查 Scrap Heap。如果有可用的 ViewHolder,就直接复用。
- 销毁: 当 ViewHolder 离开屏幕完全不可见时,会从 Scrap Heap 中移除,并根据情况放入 View Cache 或 Recycler Pool。
具体使用流程示例
假设有一个列表需要显示 100 个项目,但屏幕只能显示 10 个项目:
- 初始显示:
- RecyclerView 创建并显示前 10 个项目的 ViewHolder,并缓存到 View Cache 中。
- 向下滚动:
- 当第一个项目离开屏幕时,它的 ViewHolder 被放入 View Cache。
- 第11个项目进入屏幕时,RecyclerView 检查 View Cache,如果有可用的 ViewHolder 就复用,否则从 Recycler Pool 获取或创建新的 ViewHolder。
- 缓存满时的处理:
- 如果 View Cache 已满,多余的 ViewHolder 会被移入 Recycler Pool。
- 当屏幕上项目频繁进出时,暂时不可见的 ViewHolder 会放入 Scrap Heap,以便快速复用。
基本使用
-
实现Adapter和ViewHolder
继承RecyclerView.Adapter,设置ViewHolder泛型,ViewHolder一般放在Adapter的内部类
-
编写ViewHolder和Adapter
实现Adapter 和ViewHolder的抽象方法
这里使用ViewBinding,得去build.gradle的android {}标签下开启
viewBinding { enabled = true }
Adapter例子:
class WordAdapter(var dataSet: List<Word>) : RecyclerView.Adapter<WordAdapter.WordViewHolder>() { override fun getItemCount() = dataSet.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder { // 可以根据 viewType 生成不同的Holder,让列表内容多样化 return WordViewHolder.create(parent) } override fun onBindViewHolder(holder: WordViewHolder, position: Int) { val item = dataSet[position] holder.bind(item.word) } class WordViewHolder(private val binding: RecyclerviewItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(text: String?) { binding.textView.text = text } companion object { fun create(parent: ViewGroup): WordViewHolder { val binding = RecyclerviewItemBinding.inflate(LayoutInflater.from(parent.context)) return WordViewHolder(binding) } } } }
-
RecyclerView设置Adapter
val wordAdapter = WordAdapter(ArrayList()) val recyclerview = binding.recyclerview recyclerview.adapter = wordAdapter
-
RecyclerView设置布局管理器
recyclerview.layoutManager = LinearLayoutManager(this)
优缺点
优点
有回收和复用,速度快,效率高
缺点
需要额外的空间实现缓存机制,不过问题不大
复用机制
对照着图看代码
流程图
时序图
回收机制
流程图
时序图
布局流程
dispatchLayoutStep1
- Adapter的更新;
- 决定该启动哪种动画;
- 保存当前View的信息(getLeft(), getRight(), getTop(), getBottom()等);
- 如果有必要,先跑一次布局并将信息保存下来。
dispatchLayoutStep2
真正对子View做布局的地方。
- 计算锚点,以锚点开始填充RecyclerView(其实就是执行fill方法)。
- 执行fill方法,判断RecyclerView是否还有空间,如果有,执行layoutChunk方法,直至填充满。
- layoutChunk方法中,寻找到当前要添加的子view,add到RecyclerView中。
- 对子view进行measure和layout。
dispatchLayoutStep3
为动画保存View的相关信息; 触发动画; 相应的清理工作。
其实dispatchLayoutStep3()就是做了一些收尾工作,将一些变量重置,处理下动画。
mState.mLayoutStep
- 初始化为STEP_START
- 执行完dispatchLayoutStep1后,mState.mLayoutStep = State.STEP_LAYOUT;
- 执行完dispatchLayoutStep2后,mState.mLayoutStep = State.STEP_ANIMATIONS;
- 执行完dispatchLayoutStep3后,mState.mLayoutStep = State.STEP_START;