7.3 RecyclerView初体验


7.3 RecyclerView 初体验

本节摘要:RecyclerView 用"一个格子模板加数据流"替代"每个条目一个控件",屏幕外的格子被回收复用,千条与十条内存相当。本节为战绩列表写齐 Adapter、ViewHolder 与条目布局,接上点击查看单局详情,完成本册最大的一次思维升级。

为什么 ScrollView 塞不下战绩

3.3 节留了道禁令:别把列表塞进 ScrollView。原因算笔账就懂:一百条战绩若每条一个 TextView,LayoutInflater 要真实创建一百个控件、全部驻留内存、全部参与布局测量;一万条就是一万个。而用户屏幕同一时刻只看得见七八条——为看不见的九千九百多条付内存与布局账,是 ScrollView 方案的结构性浪费。RecyclerView 的解法是把方向倒过来:不问"每条数据配一个控件",改问"这个屏幕格子该显示哪条数据"。格子滑出屏幕进回收池,新数据滑进来时从池里领一个旧格子填上新内容——复用的是格子,流动的是数据。

三件套:item 布局、ViewHolder、Adapter

第一件,格子模板 item_record.xml——每条战绩显示序号、猜测值与结果标签,一份普通的约束布局:

<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/space_m"> <TextView android:id="@+id/tv_index" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" tools:text="第 3 次" /> <TextView android:id="@+id/tv_value" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toEndOf="@id/tv_index" android:layout_marginStart="@dimen/space_s" tools:text="猜 47" /> <TextView android:id="@+id/tv_judge" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" tools:text="太大了" /> </androidx.constraintlayout.widget.ConstraintLayout>

tools:text 只在设计预览可见、运行时不显示,是写列表模板的标准习惯。第二、三件,ViewHolder 握住格子里的控件引用,Adapter 回答两个问题并负责填格子:

class RecordAdapter(private val records: List<Record>) : RecyclerView.Adapter<RecordAdapter.VH>() { class VH(val binding: ItemRecordBinding) : RecyclerView.ViewHolder(binding.root) override fun getItemCount() = records.size // 问题一:一共几条 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val binding = ItemRecordBinding .inflate(LayoutInflater.from(parent.context), parent, false) return VH(binding) // 没格子可领时新造一个 } override fun onBindViewHolder(holder: VH, position: Int) { val r = records[position] // 问题二:第 i 条塞什么 holder.binding.tvIndex.text = "第 ${position + 1} 次" holder.binding.tvValue.text = "猜 ${r.value}" holder.binding.tvJudge.text = r.judge } }

最后在 Activity 里三行点火:

binding.rvRecords.layoutManager = LinearLayoutManager(this) binding.rvRecords.adapter = RecordAdapter(game.records)

LayoutManager 决定排布方式——LinearLayoutManager 竖排、GridLayoutManager 网格、横向列表换个参数,同一套 Adapter 与格子模板通吃,这是" RecyclerView "名字里 Recycling 之外的另一半价值。

复用机制与点击

onCreateViewHolder 与 onBindViewHolder 的分工就是复用机制的全部:前者造格子、只在回收池空时被调用,后者填格子、每条数据滑进屏幕都调用一次。日志实证留给 8.2 节——在两个方法里各打一条 Log,滚动百条列表你会发现 create 只执行了十来次,bind 却密集发生。

条目点击接在第 3 章学的监听器上,位置从 layoutPosition 拿:

holder.binding.root.setOnClickListener { onItemClicked(records[holder.layoutPosition]) }

这里有个数据更新必考点:猜对新的一局后调 adapter.notifyDataSetChanged() 能全量刷新,但正规姿势是精确通知——notifyItemInserted(records.size - 1) 只刷新新增那条,还自带默认插入动画。数据变了不 notify 则界面纹丝不动,这"忘通知"是列表新三大高频 bug 之首。

图:格子回收复用的循环

图:格子回收复用的循环

本节要点回顾

  • 结构性节约:格子随滑随收,千条数据的内存账与十条相当。
  • 三件套分工:item 模板定长相、ViewHolder 握引用、Adapter 答两问(几条、第 i 条塞什么)。
  • LayoutManager 换排布:线性网格横竖,一套 Adapter 通吃。
  • 点击用 layoutPosition:比直接存 position 抗数据变动。
  • 精确通知优于全量:notifyItemInserted 带动画,notifyDataSetChanged 是下策。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U