Flutter/Dart提取CSV列表指定行实现交互功能方法
Flutter CSV行交互功能实现方案
首先统一约定CSV列索引(可根据你的实际表结构调整):
- 索引0:需要展示的文本内容
- 索引1:背景图资源文件名(如
01.jpg) - 索引2:问题文本
- 索引3、4:问题对应的两个选项
核心状态定义
在承载交互的CSVActionsRoute/YetAnotherRoute页面对应的State类中,定义状态变量即可:
// 解析好的CSV全局数据,可通过路由传参拿到 late List<List<dynamic>> csvData; // 当前展示的行索引,初始为0对应第一行,有表头则改为1 int currentRowIndex = 0; // 当前生效的背景图,为空时展示默认背景 String? currentBgImage; // 当前展示的文本内容 String currentDisplayText = '';
在initState生命周期中完成初始值赋值:
@override void initState() { super.initState(); // 初始加载第一行内容 final firstRow = csvData[currentRowIndex]; currentDisplayText = firstRow[0].toString().trim(); // 初始行背景判断 final firstRowImg = firstRow[1].toString().trim(); currentBgImage = firstRowImg.isEmpty ? null : firstRowImg; // 初始行如果有问题,首帧加载完弹框 final firstRowQuestion = firstRow[2].toString().trim(); if (firstRowQuestion.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) => showQuestionDialog(firstRow)); } }
1. 按钮点击行切换逻辑
给交互按钮绑定如下点击回调,每次触发行号+1,加边界判断防止数组越界:
void onNextRowClick() { // 已经到最后一行直接返回,不做处理 if (currentRowIndex >= csvData.length - 1) return; setState(() { currentRowIndex += 1; final currentRow = csvData[currentRowIndex]; // 更新展示文本 currentDisplayText = currentRow[0].toString().trim(); // 更新背景图状态 final imgName = currentRow[1].toString().trim(); currentBgImage = imgName.isEmpty ? null : imgName; }); // 行更新后判断是否需要弹问题框 final question = csvData[currentRowIndex][2].toString().trim(); if (question.isNotEmpty) { // 等页面渲染完成再弹框,避免同帧渲染冲突 WidgetsBinding.instance.addPostFrameCallback((_) => showQuestionDialog(csvData[currentRowIndex])); } }
2. 动态背景实现
页面根布局用Stack叠放,最底层放背景组件,根据currentBgImage的值切换资源,加错误兜底避免资源加载失败白屏:
@override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 背景层 Positioned.fill( child: currentBgImage == null ? Image.asset( 'assets/images/default_bg.jpg', fit: BoxFit.cover, ) : Image.asset( 'assets/images/$currentBgImage', fit: BoxFit.cover, // 资源不存在时回退默认背景 errorBuilder: (_, __, ___) => Image.asset( 'assets/images/default_bg.jpg', fit: BoxFit.cover, ), ), ), // 上层内容区:放文本展示组件、交互按钮 Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(currentDisplayText, style: TextStyle(fontSize: 18)), SizedBox(height: 24), ElevatedButton( onPressed: onNextRowClick, child: Text('下一行'), ) ], ), ) ], ), ); }
注意:所有用到的图片资源需要提前在pubspec.yaml的assets节点下配置路径,否则会加载失败。
3. 问题弹窗实现
封装通用弹窗方法,传入当前行数据读取问题和选项:
void showQuestionDialog(List<dynamic> rowData) { final question = rowData[2].toString().trim(); final option1 = rowData[3].toString().trim(); final option2 = rowData[4].toString().trim(); showDialog( context: context, barrierDismissible: false, // 可选:点击遮罩不关闭,必须选完才能进下一页 builder: (context) => AlertDialog( title: Text(question), actions: [ TextButton( onPressed: () { // 这里写选中第一个选项的业务逻辑 Navigator.pop(context); }, child: Text(option1), ), TextButton( onPressed: () { // 这里写选中第二个选项的业务逻辑 Navigator.pop(context); }, child: Text(option2), ), ], ), ); }
踩坑提示
- 如果CSV文件带表头行,记得把
currentRowIndex初始值改为1,跳过表头数据 - 所有CSV读取出来的字段转字符串后必须加
trim(),避免因为单元格存在不可见空格、换行导致空判断失效 - 文件名大小写敏感,CSV里写的图片名必须和
assets目录下的实际文件名完全一致,否则会加载失败 - 路由传参时如果用
Navigator传递csvData,大列表不要做冗余序列化,直接传引用即可
内容的提问来源于stack exchange,提问作者Texter
相关产品推荐
相关产品推荐

