- 示例工程
- 移动开发
【免费下载链接】flutter-examples
[Examples] Simple basic isolated apps, for budding flutter devs.
导读
本文以 flutter-examples 仓库中的 infinite_list 独立示例应用为核心,系统讲解在 Flutter 中实现"无限列表"(Infinite List)的完整思路:借助ListView.builder的按需构建(懒加载)特性,配合数据源的动态追加,即可在保持流畅滚动性能的前提下呈现"永远加载不完"的长列表。读完本文,你将掌握惰性列表构建的核心原理、交替行布局的索引换算技巧,以及基于状态管理的收藏交互,并可直接复用 infinite_list 的工程骨架应用到自己的项目中。
一、示例项目定位与核心思想
infinite_list是 flutter-examples 仓库中一个聚焦单一功能的独立示例应用,其 README 开篇即点明项目目标:"Example app showing implementation of an infinite list"(展示无限列表实现的示例应用)。
所谓"无限列表",指的是列表内容在用户持续滚动时不断增长、看似没有终点的交互形态,典型场景包括信息流、聊天记录、日志流、随机内容生成器等。在 Flutter 中实现无限列表并不需要复杂的分页库,核心思想只有两条:
- 按需构建:列表控件只渲染当前视口内可见的条目,而非一次性构建全部数据;
- 数据按需追加:当滚动接近末尾时,为数据源补充新数据,驱动列表继续增长。
这两条核心思想在本示例中体现得极为纯粹——整个业务逻辑全部集中在单个文件 infinite_list/lib/main.dart 中,没有任何第三方状态管理依赖,非常适合初学者精读。
二、工程骨架与依赖配置
在深入代码之前,先看工程的依赖与构建配置,这决定了示例的运行前提。
2.1 依赖声明(pubspec.yaml)
infinite_list/pubspec.yaml 是本示例的依赖清单,关键内容如下:
name: infinite_list description: An infinite list using flutter environment: sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 english_words: ^3.1.0需要特别说明两点:
english_words: ^3.1.0是本示例唯一的功能性第三方依赖。它提供了generateWordPairs()等 API,用于生成随机的英文单词组合(WordPair),充当无限列表的数据来源。^3.1.0 表示允许解析 3.1.0 及以上、但小于 4.0.0 的版本。- Dart SDK 约束
>=2.12.0 <3.0.0表明该工程建立在空安全(Null Safety)时代的版本区间之上,代码采用空安全友好的写法(如使用final初始化容器、未对可变状态使用late声明)。
工程未引入任何列表分页、无限滚动类第三方库,无限列表效果完全由 Flutter 框架内置能力实现,这正是该示例的教学价值所在。
2.2 Android 端构建配置(app/build.gradle)
infinite_list/android/app/build.gradle 中,defaultConfig声明的applicationId "github.nisrulz.infinitelist"遵循了本仓库所有示例统一的包名规范github.nisrulz.<app_name>;同时minSdkVersion 16、targetSdkVersion 29、compileSdkVersion 29给出了本示例当前的 Android 最低/目标系统要求。若要在自己的机器上复现,请确保本机 Flutter SDK 版本能匹配该工程年代对应的构建链路。
2.3 获取依赖与运行
在 infinite_list 目录下依次执行:
# 1. 拉取 pubspec.yaml 中声明的依赖 flutter packages get # 2. 连接设备或模拟器后运行 flutter run仓库根目录还提供了批量脚本 get_packages.sh,它会遍历所有含pubspec.yaml的子目录并逐个执行flutter packages get,适合一次性为整个仓库的示例工程拉取依赖。
三、核心实现剖析:ListView.builder 的懒加载机制
无限列表的灵魂在 infinite_list/lib/main.dart 的_buildSuggestions()方法中。先看状态类的数据结构(main.dart):
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _saved = Set<WordPair>(); final _biggerFont = const TextStyle(fontSize: 18.0); ... }_suggestions:已生成的 WordPair 数据源列表,它是整个无限列表的"弹药库";_saved:收藏集合,用于记录用户点击心形图标的条目;_biggerFont:列表项文字样式。
3.1 按需构建的入口:ListView.builder
Widget _buildSuggestions() { return ListView.builder( padding: const EdgeInsets.all(16.0), itemBuilder: (context, i) { // ... }); }与ListView(children: [...])一次性构建所有子项不同,ListView.builder采用惰性构建策略:itemBuilder回调只会在列表需要渲染某个索引对应的条目时才会被调用,因此无论数据源增长到多大,内存中始终只保留视口附近可见的 widget。这是 Flutter 官方推荐的性能最佳实践,也是本示例能在"无限"增长数据的同时保持流畅的根本原因。
3.2 交替行布局:索引换算技巧
itemBuilder的索引i同时涵盖了列表项和分隔线两类内容,源码通过奇偶判断实现交替布局(main.dart):
itemBuilder: (context, i) { // 奇数索引渲染一条 1 像素高的分隔线 if (i.isOdd) return Divider(); // "i ~/ 2" 整除 2:1, 2, 3, 4, 5 变为 0, 1, 1, 2, 2 // 即换算回"真实的数据条目索引",剔除分隔线占用的索引位 final index = i ~/ 2; // 如果已到达当前数据源的末尾... if (index >= _suggestions.length) { // ...则一次性追加 10 条新数据 _suggestions.addAll(generateWordPairs().take(10)); } return _buildRow(_suggestions[index]); });这段代码包含两个值得反复品味的细节:
i.isOdd返回Divider():让每条单词对之间自动出现分隔线,视觉上层次分明,且避免了额外维护一个"数据+分隔线"混合列表的成本。i ~/ 2整除运算:由于每两条"数据行"之间夹着一条分隔线,外部索引i与数据索引index满足index = i ~/ 2。例如外部索引 0、2、4 分别对应数据索引 0、1、2。源码注释(main.dart)对此有清晰说明。
3.3 数据追加:无限增长的引擎
触发"无限"的关键判断是:
if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10)); }其执行语义是:当ListView.builder滚动到当前已生成数据的末尾、需要渲染下一条尚未存在的数据时,立即调用english_words包提供的generateWordPairs()生成器,通过.take(10)截取 10 个新的 WordPair 追加到_suggestions中。由于每次触底都会补充 10 条,列表便获得了持续滚动的"燃料",从而表现为无限列表。注意这里并未调用setState——这是因为追加发生在itemBuilder同步构建流程内部,追加后立即使用_suggestions[index]取出数据渲染,数据源与渲染处于同一帧,无需触发重建。
从源码结构可以推断,这里的"每次 10 条"是一个刻意选择的较小批量:既保证触底追加的粒度足够细腻,又避免一次性生成过多数据浪费内存,是典型的懒加载数据供给节奏。
四、列表项交互:收藏与状态切换
无限列表的每一行由_buildRow()渲染(main.dart):
Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), trailing: Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); }- 数据展示:
pair.asPascalCase将随机生成的英文单词对转换为 PascalCase 格式(如 "pineappleJuice"),并以 18 号字体呈现; - 收藏切换:
trailing图标根据_saved集合是否包含当前条目,在实心红色心形(Icons.favorite)与空心灰色心形(Icons.favorite_border)之间切换; - 状态管理:点击行调用
setState增删_saved,触发 UI 刷新。这里的Set<WordPair>之所以能高效完成包含判断,是因为WordPair类型提供了基于内容(而非引用)的相等性比较。
五、收藏列表页:Navigator 路由跳转
AppBar 右侧的IconButton(图标为Icons.list)绑定_pushSaved()(main.dart),点击后通过Navigator.push与MaterialPageRoute跳转到一个独立的收藏页:
void _pushSaved() { Navigator.of(context).push( MaterialPageRoute( builder: (context) { final tiles = _saved.map( (pair) => ListTile( title: Text(pair.asPascalCase, style: _biggerFont), ), ); final divided = ListTile.divideTiles( context: context, tiles: tiles, ).toList(); return Scaffold( appBar: AppBar(title: Text('Saved lists')), body: ListView(children: divided), ); }, ), ); }收藏页的构建同样体现了良好的工程习惯:
- 使用
_saved.map(...)将收藏集合映射为ListTile列表,并用ListTile.divideTiles()在相邻条目间自动插入分隔线; - 该页面数据规模受用户主动收藏行为约束、通常较小,因此这里退化为常规的
ListView(children: ...)一次性构建,与主列表的懒加载形成"按数据规模选择合适的构建策略"的对照示范; - 整个导航使用
MaterialPageRoute推入新路由,保留了平台默认的页面转场动画与返回手势。
六、应用入口与主题配置
入口 main.dart 保持了一贯的简洁:
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Infinite List', theme: ThemeData( primaryColor: Colors.blue, accentColor: Colors.lightBlue), home: RandomWords(), ); } }ThemeData定义了以蓝色为主色、浅蓝为强调色的 Material 主题;home: RandomWords()直接将有状态组件RandomWords(其状态类RandomWordsState承载全部列表逻辑)作为首页,未引入路由表,结构清晰。
七、源码证据与延伸学习
| 关注点 | 仓库路径 | 说明 |
|---|---|---|
| 无限列表全部业务逻辑 | infinite_list/lib/main.dart | ListView.builder懒加载、数据追加、收藏交互、路由跳转 |
| 依赖声明 | infinite_list/pubspec.yaml | english_words提供随机单词对数据源 |
| Android 构建配置 | infinite_list/android/app/build.gradle | 包名github.nisrulz.infinitelist、SDK 版本要求 |
| 批量拉取依赖脚本 | get_packages.sh | 遍历仓库所有示例工程执行flutter packages get |
| 同类列表示例 | using_listview/lib/main.dart | 同仓库另一聚焦ListView的独立示例,可对照学习 |
延伸建议:掌握本示例后,可进一步探索 Flutter 列表性能优化的进阶主题——例如itemExtent(固定条目高度时的滚动加速)、ListView.separated(原生分隔线构造器,可替代本文的奇偶行技巧)、ScrollController监听滚动位置做更精细的分页触发,以及GridView.builder在网格布局中的懒加载用法。本示例的"按需构建 + 触底追加"范式,是理解这些进阶能力的最佳起点。
- 示例工程
- 移动开发
【免费下载链接】flutter-examples
[Examples] Simple basic isolated apps, for budding flutter devs.
相关推荐
基于 react-infinite-scroll-component 实现滚动自动加载:antd List 无限滚动示例(infinite-load)深度拆解
基于 react infinite scroll component 实现滚动自动加载:antd List 无限滚动示例(infinite load)深度拆解
前端UI组件设计系统Ant Design List 无限滚动加载实战:结合 react-infinite-scroll-component 实现滚动自动加载列表
Ant Design List 无限滚动加载实战:结合 react infinite scroll component 实现滚动自动加载列表 导读 本文聚焦 A
前端UI组件设计系统Ant Design List 虚拟列表实战:基于 @rc-component/virtual-list 渲染无限长列表
Ant Design List 虚拟列表实战:基于 @rc component/virtual list 渲染无限长列表 在数据量大、单条行高固定、滚动频繁的列
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考