- 示例工程
- 移动开发
【免费下载链接】Flutter-Notebook
FlutterDemo合集,今天你fu了吗
本篇文章基于 Flutter-Notebook 仓库中 expansion_demo 示例 的源码与注释,系统讲解 Flutter 中两类可展开(expansion)控件的完整用法:单条目展开的ExpansionTile与多条目分组展开的ExpansionPanelList。读完本文,你将掌握这两类控件的核心参数、展开状态的管理方式、与ListView配合时的状态保留技巧,以及实际使用中的关键注意事项,可直接落地到自己的项目中。
一、示例工程概览
expansion_demo 是 Flutter-Notebook 仓库(mecury_project/example/目录)下众多 Flutter 控件演示工程之一,其文档定位非常明确:"flutter 中可展开控件(expansion)系列,使用方法及注意事项已在注释中给出"。也就是说,这个 Demo 的技术精华不在于 README 本身,而在于 lib 目录 下两个演示文件中的逐行中文注释:
expansion_tile.dart:演示单条可展开列表项ExpansionTile;expansion_panel_list.dart:演示多条目展开面板ExpansionPanelList。
两个文件分别对应 README 中的两张运行效果截图。工程入口 main.dart 通过切换home属性来选择展示哪个页面:
void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData.dark(), // home: ExpansionPanelListDemo(), home: ExpansionTileDemo(), ); } }从源码结构看,默认展示的是ExpansionTileDemo,想要查看面板列表效果时,取消home: ExpansionPanelListDemo()的注释并注释掉ExpansionTileDemo()即可,这是阅读本示例时最直接的切换方式。
该工程依赖非常精简(见 pubspec.yaml),仅使用flutterSDK 与cupertino_icons,无任何第三方可展开控件库,因此非常适合作为学习 Flutter 官方 Material 展开控件的入门样例。
二、ExpansionTile:单条可展开列表项
2.1 基础用法
ExpansionTile是"最基础的展开小部件"(源码注释原话)。它的用法非常简单:将需要被展开的部件放在children中即可,其余属性与ListTile十分相似。示例核心代码如下(见 expansion_tile.dart):
ExpansionTile( title: Text('Expansion Tile'), leading: Icon(Icons.ac_unit), backgroundColor: Colors.white12, children: <Widget>[ ListTile( title: Text('list tile'), subtitle: Text('subtitle'), ), ], // initiallyExpanded: true, ),2.2 核心属性逐个解析
结合源码注释与 Flutter 框架标准 API,ExpansionTile的常用属性及其作用如下:
| 属性 | 作用 | 示例中的取值 |
|---|---|---|
title | 折叠时显示的标题,是列表项的必填内容 | Text('Expansion Tile') |
leading | 标题左侧的前导图标,用法与ListTile一致 | Icon(Icons.ac_unit) |
trailing | 右侧的小箭头,可自行替换为其他部件 | 未设置,使用默认展开箭头 |
backgroundColor | 展开时的背景色,配合展开动画过渡 | Colors.white12 |
children | 被展开后显示的子部件列表 | 一个ListTile |
initiallyExpanded | 初始状态是否展开,默认为false(不展开) | 示例中以注释形式给出 |
2.3 展开动画与交互细节
源码注释指出一个容易被忽略的细节:当ExpansionTile被展开时,可以看到backgroundColor会进行一个 transition 动画进行过渡。这意味着背景色不是瞬间切换,而是跟随展开动画平滑渐变,在设计深色主题或强调展开状态的 UI 时可以利用这一点。
2.4 关键注意事项:ListView 中的 PageStorageKey
源码注释特别强调了一个实战中极易踩坑的问题:
当一个 ListView 中有多个 ExpansionTile 的时候,需要给每一个 ExpansionTile 指定唯一的
PageStorageKey,以保证在滑动的过程中,能够记住 ExpansionTile 的开关状态。
原因在于:ListView 的懒加载机制会销毁/复用滑出屏幕的列表项,如果没有稳定的 key,滚动回来后ExpansionTile的展开状态就会丢失。通过PageStorageKey可以把每个条目的开关状态持久化保存,这正是官方文档与社区实践中推荐的做法。示例用法:
ListView( children: <Widget>[ ExpansionTile( key: PageStorageKey('tile-1'), title: Text('Item 1'), children: ..., ), ExpansionTile( key: PageStorageKey('tile-2'), title: Text('Item 2'), children: ..., ), ], )三、ExpansionPanelList:多条目展开面板
3.1 与 ExpansionTile 的定位差异
如果说ExpansionTile是"单兵作战"的展开项,那么ExpansionPanelList就是"集团作战"的展开面板——它在一个容器内管理多个ExpansionPanel条目的展开/收起。示例工程中实现了 10 个面板的列表(见 expansion_panel_list.dart)。
3.2 使用前提(源码注释强调的三点)
源码注释对ExpansionPanelList给出了三条明确的注意事项:
ExpansionPanelList必须放在可滑动组件中使用——示例中将其包在SingleChildScrollView内,因为面板展开后内容会增长,需要滚动容器来承载;ExpansionPanel只能在ExpansionPanelList中使用——它是专属于面板列表的子控件,不能脱离容器单独使用;- 除了
ExpansionPanel还有一种特殊的ExpansionPanelRadio,同样只能在ExpansionPanelList中使用,用于实现"同一时刻只展开一个面板"的互斥效果。
3.3 展开状态管理:核心难点
与ExpansionTile自带状态管理不同,ExpansionPanelList是无状态的——每个面板是否展开由外部数据驱动。示例通过一个ExpandStateBean数据模型来保存每个面板的展开状态:
class ExpandStateBean { var isOpen; var index; ExpandStateBean(this.index, this.isOpen); }初始化时创建 10 个面板的状态对象,初始全部为收起状态:
_ExpansionPanelListDemoState() { mList = new List(); expandStateList = new List(); for (int i = 0; i < 10; i++) { mList.add(i); expandStateList.add(ExpandStateBean(i, false)); } }用户点击面板时,ExpansionPanelList的expansionCallback会回调被点击面板的索引与当前展开状态,示例通过setState反转对应面板的isOpen,从而驱动 UI 刷新:
_setCurrentIndex(int index, isExpand) { setState(() { expandStateList.forEach((item) { if (item.index == index) { item.isOpen = !isExpand; } }); }); }这里expansionCallback: (index, bol) => _setCurrentIndex(index, bol)的参数bol表示"该面板当前的展开状态",取反后即为目标状态,实现点击展开、再次点击收起。
3.4 完整示例代码
面板列表的完整构建逻辑如下(headerBuilder负责渲染折叠时的头部,body负责渲染展开后的内容,isExpanded从状态列表中读取):
body: SingleChildScrollView( child: ExpansionPanelList( expansionCallback: (index, bol) { _setCurrentIndex(index, bol); }, children: mList.map((index) { return new ExpansionPanel( headerBuilder: (context, isExpanded) { return new ListTile( title: new Text('This is NO. $index'), ); }, body: ListTile( title: Text('expansion no.$index'), ), isExpanded: expandStateList[index].isOpen, ); }).toList(), ), )3.5 扩展:ExpansionPanelRadio 的互斥模式
若需要"手风琴"式交互(打开一个面板自动收起其他面板),可以使用ExpansionPanelRadio。从框架 API 看,它通过value标识每个面板,配合ExpansionPanelList的expandedHeaderPadding等参数使用,展开时自动互斥。需要说明的是,示例工程本身未提供该模式的运行代码,此处仅作概念性补充,具体实现可查阅 Flutter 官方 API 文档。
四、运行与验证
若想在本地运行本示例,需要先安装 Flutter SDK,然后在mecury_project/example/expansion_demo/目录下执行:
flutter run工程入口 main.dart 已配置好MaterialApp(采用ThemeData.dark()深色主题),切换home即可分别体验ExpansionTileDemo与ExpansionPanelListDemo两个页面。
关于测试:工程自带的 widget_test.dart 是 Flutter 脚手架生成的默认计数器 smoke test,并未针对展开控件编写断言。从源码看,若要为展开控件补充测试,可以基于WidgetTester模拟点击ExpansionTile/面板头部,再通过find.text校验展开后的子部件是否出现——这与脚手架测试中tester.tap+tester.pump的交互模式一脉相承。
五、小结
回到本示例文档的主题——"可展开控件(expansion)系列",可以提炼出两条核心经验:
- 单条展开选
ExpansionTile:开箱即用、自带状态与动画,放在ListView中时记得用唯一的PageStorageKey保住开关状态; - 多条分组展开选
ExpansionPanelList:必须配合可滑动容器使用,展开状态由外部数据驱动,需要自行维护状态列表并通过expansionCallback更新,互斥场景使用ExpansionPanelRadio。
这两类控件覆盖了绝大多数"折叠/展开"交互场景,本仓库的 expansion_demo 以其详尽的注释提供了教科书级别的参考实现,值得在开发中反复查阅对照。
- 示例工程
- 移动开发
【免费下载链接】Flutter-Notebook
FlutterDemo合集,今天你fu了吗
相关推荐
Flet ExpansionTile 控件完全指南:Python 实现可折叠列表项的展开、动画与主题定制
Flet ExpansionTile 控件完全指南:Python 实现可折叠列表项的展开、动画与主题定制 导读 ExpansionTile(展开瓦片)是 Fle
前端跨平台桌面应用移动开发Flet ExpansionPanel 与 ExpansionPanelList 完全指南:可折叠面板的配置、事件与实战
Flet ExpansionPanel 与 ExpansionPanelList 完全指南:可折叠面板的配置、事件与实战 导读 本文围绕 Flet 中的 Mat
前端跨平台桌面应用移动开发Flet ExpansionPanelList 详解:用 Python 构建可展开/折叠的 Material 面板列表
Flet ExpansionPanelList 详解:用 Python 构建可展开/折叠的 Material 面板列表 导读 ExpansionPanelLis
前端跨平台桌面应用移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考