flutter ListView Item复用源码解析

Flutter 的 ListView 的 Item 复用机制是其高性能列表渲染的核心,底层实现依赖于 Flutter 的渲染管线、Element 树和 Widget 树的协调机制。以下是 ListView 复用机制的源码级解析,结合关键类和核心逻辑进行分析。


1. ListView 的底层结构

ListView 的复用机制是通过 Sliver 系列组件实现的,具体在 SliverList 和 SliverChildBuilderDelegate 中完成。以下是关键类的关系:

  • ListView:基于 ScrollView,内部使用 SliverList 实现滚动布局。

  • SliverList:继承自 SliverMultiBoxAdaptorWidget,负责管理子组件的布局和复用。

  • SliverChildBuilderDelegate:实现 SliverChildDelegate 接口,负责按需构建子组件(即 itemBuilder)。


2. 复用机制的核心:SliverChildDelegate

ListView.builder 使用的 SliverChildBuilderDelegate 是复用机制的核心。其核心逻辑在以下两个方法中:

(1) build 方法:按需构建子组件

当用户滚动时,SliverList 会通过 SliverChildDelegate 的 build 方法请求可见区域的子组件。例如:

// SliverChildBuilderDelegate 的 build 方法
Widget build(BuildContext context, int index) {return widget.itemBuilder(context, index); // 调用用户定义的 itemBuilder
}

关键点build 方法仅在需要显示某个子组件时被调用(懒加载)。


(2) createChild 和 didFinishLayout:复用子组件

在 RenderSliverListSliverList 的渲染对象)中,滚动时会触发以下逻辑:

// RenderSliverList 的 performLayout 方法
void performLayout() {// 计算可见区域的子组件范围final int firstIndex = _calculateFirstIndex();final int lastIndex = _calculateLastIndex();// 回收不可见的子组件collectGarbage(...);// 按需创建或复用子组件for (int index = firstIndex; index <= lastIndex; index++) {if (child == null) {child = createChild(index); // 创建或复用子组件}// 布局子组件...}
}
  • createChild:尝试从缓存池(childManager)中获取可复用的子组件。

  • collectGarbage:将滑出屏幕的子组件放入缓存池,供后续复用。


3. Element 树的复用

Flutter 的复用机制不仅仅是对 Widget 的复用,更重要的是对 Element 树的复用。Element 是 Widget 和 RenderObject 之间的桥梁,负责管理状态和生命周期。

(1) Element 的复用条件

当 ListView 滚动时,Flutter 会检查新旧 Widget 的类型和 Key 是否一致:

// Element 的 updateChild 方法
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {if (newWidget == null) {// 移除旧 Elementreturn null;}if (child != null) {// 如果新旧 Widget 类型和 Key 匹配,复用 Elementif (child.widget == newWidget) {return child;}if (Widget.canUpdate(child.widget, newWidget)) {child.update(newWidget);return child;}}// 创建新的 Elementreturn inflateWidget(newWidget, newSlot);
}

关键点:如果新旧 Widget 的 runtimeType 和 Key 相同,则复用 Element,否则销毁旧的并创建新的。


(2) Widget 的不可变性

由于 Widget 是不可变的(immutable),复用时 Element 会直接更新到新的 Widget 实例,但底层的 RenderObject(如布局和绘制信息)可能被复用。


4. 缓存池:ChildVendor

在 SliverMultiBoxAdaptorWidget 中,ChildVendor 负责管理子组件的缓存池:

// ChildVendor 的 collectGarbage 方法
void collectGarbage(int leadingGarbage, int trailingGarbage) {// 将超出可见范围的子组件放入缓存池while (leadingGarbage > 0) {final Element element = _children.removeAt(0);_detachChild(element);leadingGarbage--;}// 类似处理 trailingGarbage...
}
  • 缓存策略:缓存池的大小受 cacheExtent 参数控制(默认缓存一屏外的子组件)。


5. 性能优化关键点

(1) Key 的作用

如果列表项的顺序可能变化,或需要保持状态,必须为每个子组件指定唯一的 Key。例如:

ListView.builder(itemBuilder: (context, index) {return MyItem(key: ValueKey(items[index].id)); // 唯一 Key},
);
  • 源码中的 Key 匹配:在 Element 的 updateChild 方法中,Key 是判断是否复用的关键条件。

 static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}
(2) 避免不必要的重建
  • 使用 const 构造函数减少 Widget 实例的创建。

  • 避免在 itemBuilder 中创建新的闭包或对象。


6. 源码流程总结

  1. 滚动触发布局:用户滚动时,RenderSliverList 的 performLayout 被调用。

  2. 计算可见范围:确定需要显示的子组件索引(firstIndex 和 lastIndex)。

  3. 回收不可见子组件:通过 collectGarbage 将滑出屏幕的子组件放入缓存池。

  4. 复用或创建子组件:通过 createChild 从缓存池获取或新建子组件。

  5. 更新 Element 树:检查新旧 Widget 是否可复用,更新 Element 和 RenderObject


总结

Flutter 的 ListView 复用机制通过以下核心设计实现高性能:

  • 懒加载:仅构建可见区域的子组件。

  • Element 复用:通过 Key 和 Widget 类型匹配复用 Element

  • 缓存池:通过 ChildVendor 管理滑出屏幕的子组件。

  • 不可变 Widget:快速更新 Element,复用底层的 RenderObject

通过理解源码逻辑,开发者可以更好地优化列表性能(如合理使用 Key、避免不必要的重建)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69606.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

粒子群优化算法:像鸟群一样找到最优解

前言 在人工智能的浩瀚星空中,粒子群优化算法(PSO)如同一颗熠熠生辉的明星,吸引了无数科研人员的目光。它的名字听起来好像非常高大上,仿佛只有数学天才和算法大师才能理解。但实际上,PSO的原理并没有那么复杂。想象一下,一群聪明的小鸟在天空中自由飞翔,大家互相呼唤…

QT修仙之路2-2 对话框 尚欠火候

警告对话框 相关代码 错误对话框 相关代码 消息对话框 相关代码 询问对话框 相关代码 相关代码 警告对话框 QMessageBox::warning(this,"错误","账号密码不能为空",QMessageBox::Ok);错误对话框 QMessageBox msgBox(QMessageBox::Critical,"错误…

Python 字典(一个简单的字典)

在本章中&#xff0c;你将学习能够将相关信息关联起来的Python字典。你将学习如何访问和修改字典中的信息。鉴于字典可存储的信息量几乎不受限制&#xff0c;因此我们会演示如何遍 历字典中的数据。另外&#xff0c;你还将学习存储字典的列表、存储列表的字典和存储字典的字典。…

conda 修复 libstdc++.so.6: version `GLIBCXX_3.4.30‘ not found 简便方法

ImportError: /data/home/hum/anaconda3/envs/ipc/bin/../lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /home/hum/anaconda3/envs/ipc/lib/python3.11/site-packages/paddle/base/libpaddle.so) 1. 检查版本 strings /data/home/hum/anaconda3/envs/ipc/…

RTD2775QT/RTD2795QT瑞昱显示器芯片方案

RTD2775QT与RTD2795QT&#xff1a;高性能4K显示驱动芯片 RTD2775QT与RTD2795QT是瑞昱半导体公司推出的两款高性能显示驱动芯片&#xff0c;专为满足现代显示设备对高清、高分辨率的需求而设计。这两款芯片不仅支持4K分辨率&#xff0c;还具备丰富的功能和卓越的性能&#xff0…

Linux路径中的‘~‘

本文来自DeepSeek 在Linux中&#xff0c;~ 是用户主目录的简写。具体含义如下&#xff1a; 当前用户的主目录&#xff1a; ~ 代表当前登录用户的主目录。例如&#xff0c;用户 alice 的主目录通常是 /home/alice&#xff0c;~ 就指向 /home/alice。 其他用户的主目录&#xff…

【含开题报告+文档+PPT+源码】学术研究合作与科研项目管理应用的J2EE实施

开题报告 本研究构建了一套集注册登录、信息获取与科研项目管理于一体的综合型学术研究合作平台。系统用户通过注册登录后&#xff0c;能够便捷地接收到最新的系统公告和科研动态新闻&#xff0c;并能进一步点击查看详尽的新闻内容。在科研项目管理方面&#xff0c;系统提供强…

ES6 Proxy 用法总结以及 Object.defineProperty用法区别

Proxy 是 ES6 引入的一种强大的拦截机制&#xff0c;用于定义对象的基本操作&#xff08;如读取、赋值、删除等&#xff09;的自定义行为。相较于 Object.defineProperty&#xff0c;Proxy 提供了更灵活、全面的拦截能力。 1. Proxy 语法 const proxy new Proxy(target, hand…

力扣 单词拆分

动态规划&#xff0c;字符串截取&#xff0c;可重复用&#xff0c;集合类。 题目 单词可以重复使用&#xff0c;一个单词可用多次&#xff0c;应该是比较灵活的组合形式了&#xff0c;可以想到用dp&#xff0c;遍历完单词后的状态的返回值。而这里的wordDict给出的是list&…

Node.js 环境配置

什么是 Node.js Node.js 是一个基于 Chrome V8 JavaScript 引擎的 JavaScript 运行时环境&#xff0c;它允许你在服务器端运行 JavaScript。传统上&#xff0c;JavaScript 主要用于浏览器中的前端开发&#xff0c;而 Node.js 使得 JavaScript 也能够在服务器上执行&#xff0c;…

Redis企业开发实战(四)——点评项目之分布式锁

目录 一、分布式锁介绍 (一)分布式锁基本介绍 (二)分布式锁满足的条件 (三)常见的分布式锁 1.Mysql 2.Redis 3.Zookeeper 二、Redis分布式锁详解 (一)Redis分布式锁的实现核心思路 获取锁&#xff1a; 释放锁&#xff1a; (二)基于Redis实现分布式锁初级版本 1.…

kalilinu渗透测试全流程方案

1. 准备阶段 1.1 目标确认与风险评估 本方案旨在教育作用&#xff0c;请勿在没有授权的情况下使用 2. 信息收集 2.1 被动信息收集 2.1.1 DNS侦察 子域名枚举&#xff1a;# 使用Amass进行子域名枚举 amass enum -d example.com# 使用Subfinder进行子域名发现 subfinder -d…

【个人开发】cuda12.6安装vllm安装实践【内含踩坑经验】

1. 背景 vLLM是一个快速且易于使用的LLM推理和服务库。企业级应用比较普遍&#xff0c;尝试安装相关环境&#xff0c;尝试使用。 2. 环境 模块版本python3.10CUDA12.6torch2.5.1xformers0.0.28.post3flash_attn2.7.4vllm0.6.4.post1 2.1 安装flash_attn 具体选择什么版本&…

面试准备——Java理论高级【笔试,面试的核心重点】

集合框架 Java集合框架是面试中的重中之重&#xff0c;尤其是对List、Set、Map的实现类及其底层原理的考察。 1. List ArrayList&#xff1a; 底层是动态数组&#xff0c;支持随机访问&#xff08;通过索引&#xff09;&#xff0c;时间复杂度为O(1)。插入和删除元素时&#…

【鸿蒙开发】第二十四章 AI -Natural Language Kit(自然语言理解服务)

目录 1 简介 2 约束与限制 3. 分词 3.1 适用场景 3.2 约束与限制 3.3 开发步骤 3.4 开发实例 4 实体抽取 4.1 适用场景 4.2 约束与限制 4.3 开发步骤 4.4 开发实例 1 简介 Natural Language Kit&#xff08;自然语言理解服务&#xff09;提供了多项文本语义理解相…

系统思考—自我超越

“我不在乎你从哪里开始&#xff0c;我只在乎你能走到哪里。真正的超越在于敢于突破自己设定的框架。” —— 亚伯拉罕林肯 在今天这个快速变化的商业环境里&#xff0c;许多企业和团队都会遇到同样的挑战&#xff1a;如何突破现有的框架&#xff0c;实现真正的自我超越&#…

win11+mac键盘+PowerToys 重映射热键

在win11系统中&#xff0c;使用mac的蓝牙键盘&#xff0c;键盘本身没有PrintScreen键。这时可以借助PowerToys来将其他键映射到系统的PrintScreen. 1.下载安装PowerToys 地址https://learn.microsoft.com/zh-cn/windows/powertoys/ 2.打开PowerToys&#xff0c;选中【键盘管理器…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<8>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 今天我们复习前面学习的指针知识 目录 关于指针数组和数组指针的区别指针数组&#xff08;Array of Poi…

今日写题work01

题目一&#xff1a;轮转数组 三种思路&#xff0c;时间复杂度越优越好 第一种思路: 直接暴力求解&#xff0c;空间复杂度为o(1),但时间复杂度为o(n^2) #include <stdio.h> void rotate(int* nums, int k, int len); int main() {int arr[] { 1,2,3,4,5,6,7 };rotate(a…

原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力

原生鸿蒙版小艺APP接入DeepSeek-R1&#xff0c;为HarmonyOS应用开发注入新活力 在科技飞速发展的当下&#xff0c;人工智能与操作系统的融合正深刻改变着我们的数字生活。近日&#xff0c;原生鸿蒙版小艺APP成功接入DeepSeek-R1&#xff0c;这一突破性进展不仅为用户带来了更智…