Flutter开发:表单页保存后在首页生成圆形图表的实现
Flutter实现表单提交后首页生成多圆形图表解决方案
核心问题梳理
现有代码存在三个关键问题:
- 表单提交时跳转新首页而非返回原页面,无法传递数据
- 首页无数据存储结构,无法保存多个表单数据
- 缺少圆形图表的实现与多图表布局逻辑
1. 定义数据模型
创建GoalData类封装表单数据,方便跨页面传递与存储:
class GoalData { final String goalText; final List<String> activities; final List<bool> activityCompleted; final File? image; GoalData({ required this.goalText, required this.activities, required this.activityCompleted, this.image, }); // 计算完成率,用于圆形图表展示 double get completionRate { int completed = activityCompleted.where((isDone) => isDone).length; return completed / activityCompleted.length; } }
2. 修改Checklist表单页
关键修改点:
- 给所有TextField添加控制器,获取用户输入
- 提交时通过
Navigator.pop将数据返回首页,而非新建首页实例
import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:flutter/services.dart'; import '../utils/theme_utils.dart'; import 'dart:io'; import '../models/goal_data.dart'; class Checklist extends StatefulWidget { const Checklist({super.key}); @override _ChecklistState createState() => _ChecklistState(); } class _ChecklistState extends State<Checklist> { List<bool> checkBoxValues = [false, false, false, false, false]; File? selectedImage; final TextEditingController goalController = TextEditingController(); final List<TextEditingController> activityControllers = List.generate(5, (_) => TextEditingController()); Future<void> _pickImage() async { final imagePicker = ImagePicker(); try { final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { setState(() { selectedImage = File(pickedFile.path); }); } } on PlatformException catch (e) { print("Fehler beim Bildauswählen: $e"); } } void _submitGoal() { final goalData = GoalData( goalText: goalController.text, activities: activityControllers.map((ctrl) => ctrl.text).toList(), activityCompleted: checkBoxValues, image: selectedImage, ); Navigator.pop(context, goalData); } @override void dispose() { goalController.dispose(); for (var ctrl in activityControllers) { ctrl.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Ziel"), backgroundColor: ThemeUtils.baseColor, actions: [ IconButton( icon: const Icon(Icons.save), onPressed: _submitGoal, ), ], ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.all(16.0), child: Text( "Mein SMARTes Ziel", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all(color: Colors.grey), ), child: TextField( controller: goalController, keyboardType: TextInputType.multiline, maxLines: null, decoration: const InputDecoration( hintText: "Dein Text hier...", border: InputBorder.none, ), ), ), const Padding( padding: EdgeInsets.all(16.0), child: Text( "Aktivitäten", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), Container( margin: const EdgeInsets.all(16), child: Column( children: List.generate(5, (index) { return Row( children: [ Checkbox( value: checkBoxValues[index], onChanged: (bool? value) { setState(() { checkBoxValues[index] = value ?? false; }); }, ), Expanded( child: Container( margin: const EdgeInsets.only(left: 8), padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all(color: Colors.grey), ), child: TextField( controller: activityControllers[index], keyboardType: TextInputType.multiline, maxLines: null, decoration: InputDecoration( hintText: "Aktivität $index...", border: InputBorder.none, ), ), ), ), ], ); }), ), ), const Padding( padding: EdgeInsets.all(16.0), child: Text( "Mein Glaube", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), Container( margin: const EdgeInsets.all(16), child: GestureDetector( onTap: _pickImage, child: Container( height: 200, decoration: BoxDecoration( border: Border.all(color: Colors.grey), ), child: selectedImage != null ? Image.file(selectedImage!) : const Center( child: Text("Bild auswählen"), ), ), ), ), ], ), ), ); } }
3. 修改Homescreen首页
关键修改点:
- 添加
List<GoalData>存储多个目标数据 - 用
Navigator.push接收表单返回的数据并更新状态 - 使用
GridView实现多图表并排布局 - 集成
fl_chart实现环形进度图表
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fl_chart/fl_chart.dart'; import '../widgets/add_goal_button.dart'; import '../widgets/goal_app_bar.dart'; import '../models/goal_data.dart'; import './checklist.dart'; class HomescreenWidget extends StatefulWidget { const HomescreenWidget({Key? key}) : super(key: key); @override _HomescreenWidgetState createState() => _HomescreenWidgetState(); } class _HomescreenWidgetState extends State<HomescreenWidget> { final scaffoldKey = GlobalKey<ScaffoldState>(); late FocusNode unfocusNode; List<GoalData> goals = []; @override void initState() { super.initState(); unfocusNode = FocusNode(); } @override void dispose() { unfocusNode.dispose(); super.dispose(); } Future<void> _openChecklist() async { final result = await Navigator.push<GoalData>( context, MaterialPageRoute(builder: (context) => const Checklist()), ); if (result != null) { setState(() { goals.add(result); }); } } Widget _buildCircleChart(GoalData goal) { return SizedBox( height: 180, width: 180, child: Stack( alignment: Alignment.center, children: [ PieChart( PieChartData( sectionsSpace: 0, centerSpaceRadius: 60, sections: [ PieChartSectionData( color: Colors.grey[300], value: 1 - goal.completionRate, showTitle: false, ), PieChartSectionData( color: ThemeUtils.baseColor, value: goal.completionRate, showTitle: false, ), ], ), ), Column( mainAxisSize: MainAxisSize.min, children: [ Text( "${(goal.completionRate * 100).toInt()}%", style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), SizedBox( width: 100, child: Text( goal.goalText, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ], ), ); } @override Widget build(BuildContext context) { if (Theme.of(context).platform == TargetPlatform.iOS) { SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( statusBarBrightness: Theme.of(context).brightness, systemStatusBarContrastEnforced: true, ), ); } return GestureDetector( onTap: () => unfocusNode.canRequestFocus ? FocusScope.of(context).requestFocus(unfocusNode) : FocusScope.of(context).unfocus(), child: Scaffold( backgroundColor: Colors.white, key: scaffoldKey, appBar: GoalAppBar(), body: SafeArea( top: true, child: goals.isEmpty ? const Center(child: Text("暂无目标,点击下方按钮添加")) : GridView.builder( padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, ), itemCount: goals.length, itemBuilder: (context, index) { return _buildCircleChart(goals[index]); }, ), ), floatingActionButton: FloatingActionButton( onPressed: _openChecklist, child: const Icon(Icons.add), ), ), ); } }
4. 添加依赖
在pubspec.yaml中添加图表库依赖:
dependencies: flutter: sdk: flutter fl_chart: ^0.65.0
功能说明
- 点击首页FAB打开表单页,填写完成后点击保存按钮返回首页
- 首页自动添加一个环形进度图表,展示该目标的活动完成率
- 支持添加多个目标,图表自动按每行2个的布局排列
- 图表中心显示完成百分比和目标标题
内容的提问来源于stack exchange,提问作者user19922759
相关产品推荐
相关产品推荐

