You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:11:01