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:复用子组件
在 RenderSliverList(SliverList 的渲染对象)中,滚动时会触发以下逻辑:
// 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. 源码流程总结
-
滚动触发布局:用户滚动时,
RenderSliverList的performLayout被调用。 -
计算可见范围:确定需要显示的子组件索引(
firstIndex和lastIndex)。 -
回收不可见子组件:通过
collectGarbage将滑出屏幕的子组件放入缓存池。 -
复用或创建子组件:通过
createChild从缓存池获取或新建子组件。 -
更新 Element 树:检查新旧
Widget是否可复用,更新Element和RenderObject。
总结
Flutter 的 ListView 复用机制通过以下核心设计实现高性能:
-
懒加载:仅构建可见区域的子组件。
-
Element 复用:通过
Key和Widget类型匹配复用Element。 -
缓存池:通过
ChildVendor管理滑出屏幕的子组件。 -
不可变 Widget:快速更新
Element,复用底层的RenderObject。
通过理解源码逻辑,开发者可以更好地优化列表性能(如合理使用 Key、避免不必要的重建)。
相关文章:
flutter ListView Item复用源码解析
Flutter 的 ListView 的 Item 复用机制是其高性能列表渲染的核心,底层实现依赖于 Flutter 的渲染管线、Element 树和 Widget 树的协调机制。以下是 ListView 复用机制的源码级解析,结合关键类和核心逻辑进行分析。 1. ListView 的底层结构 ListView 的复…...
Spring Boot 配置 Mybatis 读写分离
JPA 的读写分离配置不能应用在 Mybatis 上, 所以 Mybatis 要单独处理 为了不影响原有代码, 使用了增加拦截器的方式, 在拦截器里根据 SQL 的 CRUD 来路由到不同的数据源 需要单独增加Mybatis的配置 Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) t…...
网络初识-
网络的相关概念 一、局域网和广域网 将各种计算机、外部设备等相互连接起来,实现在这个范围内数据通信和资源共享的计算机网络。它的覆盖范围通常在几百米到几公里之内。例如,一个小型企业的办公室,通过交换机将多台电脑连接在一起…...
DNS污染:网络世界的“隐形劫持”与防御
在互联网的底层架构中,DNS(域名系统)如同数字世界的“导航员”,将用户输入的域名翻译成机器可读的IP地址。然而,DNS污染(DNS Poisoning)正像一场无声的“地址篡改”危机,威胁着全球网…...
MQTT(Message Queuing Telemetry Transport)协议(三)
主题是什么 2. TCP 协议封装 tcp.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>// 建立 TCP 连接 int tcp_connect(const char *server_ip, int s…...
多核cpu与时间片多线程的问题
在多核处理器中,每个核心可以独立运行一个线程。操作系统负责管理和调度这些线程,以确保高效利用处理器资源。下面详细解释如何获取时间片以及四个线程如何在四个核心上同时工作。 ### 时间片和调度 #### 1. 时间片(Time Slice)…...
电脑出现蓝屏英文怎么办?查看修复过程
电脑出现蓝屏英文是一种常见的电脑故障,它通常表示电脑遇到了严重的错误,需要停止运行以防止进一步的损坏。电脑蓝屏英文的原因可能有很多,比如硬件故障、驱动程序错误、系统文件损坏、病毒感染等。那么,当电脑出现蓝屏英文时&…...
安卓基础(第一集)
SharedPreferences(本地存储简单数据) 在 Android 中,SharedPreferences 用于存储小型数据。 (1)存储数据 // 获取 SharedPreferences 对象 SharedPreferences sharedPreferences getSharedPreferences("MyPre…...
【从零开始入门unity游戏开发之——C#篇56】C#补充知识点——模式匹配
考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…...
【数据可视化-16】珍爱网上海注册者情况分析
🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...
c/c++蓝桥杯经典编程题100道(21)背包问题
背包问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 背包问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1:0-1背包(基础动态规划,难度★) 解法2:0-1背包(空间优化版,难度★…...
电赛DEEPSEEK
以下是针对竞赛题目的深度优化方案,重点解决频率接近时的滤波难题和相位测量精度问题: 以下是使用NI Multisim 14.3实现本项目的详细解决方案: 一、基础要求实现方案(模块化设计) 1. 双频信号发生电路 电路结构&…...
VSOMEIP ROUTING应用和CLIENT应用之间交互的消息
#define VSOMEIP_ASSIGN_CLIENT 0x00 // client应用请求分配client_id #define VSOMEIP_ASSIGN_CLIENT_ACK 0x01 // routing应用返回分配的client_id #define VSOMEIP_REGISTER_APPLICATION 0x02 // client应用注册someip应用 #…...
HTML之基本布局div|span
HTML基本布局使用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"width<device-width>, initial-scale1.0"><title>布局</title> <…...
Linux下学【MySQL】常用函数助你成为数据库大师~(配sql+实操图+案例巩固 通俗易懂版~)
绪论 每日激励:“唯有努力,才能进步” 绪论: 本章是MySQL中常见的函数,利用好函数能很大的帮助我们提高MySQL使用效率,也能很好处理一些情况,如字符串的拼接,字符串的获取,进制…...
【Rabbitmq篇】高级特性----TTL,死信队列,延迟队列
目录 一.TTL ???1.设置消息的TTL 2.设置队列的TTL 3.俩者区别? 二.死信队列 定义: 消息成为死信的原因: 1.消息被拒绝(basic.reject 或 basic.nack) 2.消息过期(TTL) 3.队列达到最大长度? …...
机器学习赋能的智能光子学器件系统研究与应用
机器学习赋能的智能光子学器件系统研究与应用 时间: 2025年03月29日-03月30日 2025年04月05日-04月06日 机器学习赋能的光子学器件与系统:从创新设计到前沿应用 课程针对光子学方面的从业科研人员及开发者,希望了解和实践在集成光学/空间…...
尚硅谷课程【笔记】——大数据之Linux【三】
课程视频链接:尚硅谷大数据Linux课程 七、定时任务调度 任务调度:指系统在某个时间执行的特定的命令或程序。 1)系统工作:有些重要的工作必须周而复始地执行。 2)个别用户工作:用户可能希望在某些特定的时…...
Visual Studio踩过的坑
统计Unity项目代码行数 编辑-查找和替换-在文件中查找 查找内容输入 b*[^:b#/].*$ 勾选“使用正则表达式” 文件类型留空 也有网友做了指定,供参考 !*\bin\*;!*\obj\*;!*\.*\*!*.meta;!*.prefab;!*.unity 打开Unity的项目 注意:只是看࿰…...
教程 | MySQL 基本指令指南(附MySQL软件包)
此前已经发布了安装教程安装教程,现在让我们来学习一下MySQL的基本指令。 一、数据库连接与退出 连接本地数据库 mysql -uroot -p # 输入后回车,按提示输入密码(密码输入不可见)若需隐藏密码显示,可使用࿱…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
算术操作符与类型转换:从基础到精通
目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...
