news 2026/5/14 18:07:51

Flutter + 开源鸿蒙实战 | 极简记账本 Day6:项目最终优化 + 鸿蒙全适配 + 项目完结总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter + 开源鸿蒙实战 | 极简记账本 Day6:项目最终优化 + 鸿蒙全适配 + 项目完结总结

🔥Flutter + 开源鸿蒙实战 | 极简记账本 Day6:项目最终优化 + 鸿蒙全适配 + 项目完结总结

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

系列项目:极简记账本(6 天完整版)

依赖库:shared_preferences(第三方本地持久化)

📌本文导读(必看)

本文是极简记账本系列最终篇,基于第三方库 shared_preferences完成完整持久化项目的最终优化、鸿蒙系统全端适配、交互体验升级、BUG 修复与项目总结。

经过六天开发,项目已实现:底部导航、新增记账、账单列表、收支统计、个人中心、数据清空、全局持久化存储。

适合:Flutter 入门练手、鸿蒙跨端开发、本地存储学习使用。

🧱一、Day6 核心任务拆解

  1. 基于shared_preferences第三方库完成全局持久化优化
  2. 统一全局主题、颜色、字体、圆角,提升界面美观度
  3. 修复页面切换不刷新、数据不同步 BUG
  4. 优化空状态、按钮交互、提示文案,提升体验
  5. 适配开源鸿蒙全面屏、状态栏、导航栏样式
  6. 项目功能总结、知识点复盘、开发思路梳理

⚙️二、核心知识点回顾

  • shared_preferences:第三方本地持久化库,实现数据真正永久存储
  • Stateful/Stateless 组件:页面状态管理与 UI 渲染
  • JSON 序列化:复杂数据本地存储解析
  • 组件传值 & 状态刷新:多页面数据同步
  • 鸿蒙跨端适配:Flutter 应用在鸿蒙系统运行规范

🥝三、依赖配置(必须配置pubspec.yaml)

dependencies: flutter: sdk: flutter shared_preferences: ^2.2.3

执行命令:

flutter pub get

🚀四、Day6 最终完整版代码(lib\main.dart)

import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '极简记账本', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.teal, useMaterial3: true, cardTheme: CardTheme(elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), ), home: const MainPage(), ); } } class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { int _currentIndex = 0; final List<Widget> _pages = [ const HomePage(), const AddPage(), const StatisticPage(), const MinePage(), ]; @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _currentIndex, children: _pages, ), bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: _currentIndex, selectedItemColor: Colors.teal, unselectedItemColor: Colors.grey, onTap: (index) => setState(() => _currentIndex = index), items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"), BottomNavigationBarItem(icon: Icon(Icons.add_box), label: "记账"), BottomNavigationBarItem(icon: Icon(Icons.bar_chart), label: "统计"), BottomNavigationBarItem(icon: Icon(Icons.person), label: "我的"), ], ), ); } } // 首页 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { List<Map<String, dynamic>> billList = []; @override void initState() { super.initState(); _loadData(); } Future<void> _loadData() async { final prefs = await SharedPreferences.getInstance(); final str = prefs.getString("billList"); if (str != null) { setState(() { billList = List<Map<String, dynamic>>.from(jsonDecode(str)); }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("账单列表")), body: billList.isEmpty ? const Center(child: Text("暂无账单记录")) : ListView.builder( padding: const EdgeInsets.all(12), itemCount: billList.length, itemBuilder: (ctx, i) { final item = billList[i]; return Card( child: ListTile( title: Text(item["title"]), subtitle: Text(item["time"]), trailing: Text( "${item["type"]} ¥${item["money"]}", style: TextStyle( color: item["type"] == "收入" ? Colors.green : Colors.red, fontWeight: FontWeight.w500, fontSize: 15, ), ), ), ); }, ), ); } } // 记账页面 class AddPage extends StatefulWidget { const AddPage({super.key}); @override State<AddPage> createState() => _AddPageState(); } class _AddPageState extends State<AddPage> { final titleController = TextEditingController(); final moneyController = TextEditingController(); String type = "支出"; Future<void> _save() async { final title = titleController.text.trim(); final money = moneyController.text.trim(); if (title.isEmpty || money.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("请填写完整信息"))); return; } final m = double.tryParse(money); if (m == null || m <= 0) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("请输入正确金额"))); return; } final data = { "title": title, "money": m, "type": type, "time": DateTime.now().toString().substring(0, 16), }; final prefs = await SharedPreferences.getInstance(); final str = prefs.getString("billList"); List list = str == null ? [] : jsonDecode(str); list.add(data); await prefs.setString("billList", jsonEncode(list)); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("保存成功 ✅"))); } titleController.clear(); moneyController.clear(); setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("新增记账")), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("备注", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField(controller: titleController, decoration: const InputDecoration(border: OutlineInputBorder())), const SizedBox(height: 16), const Text("金额", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), const SizedBox(height: 8), TextField( controller: moneyController, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(border: OutlineInputBorder()), ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Row(children: [ Radio(value: "支出", groupValue: type, onChanged: (v) => setState(() => type = v.toString())), const Text("支出"), ]), const SizedBox(width: 40), Row(children: [ Radio(value: "收入", groupValue: type, onChanged: (v) => setState(() => type = v.toString())), const Text("收入"), ]), ], ), const SizedBox(height: 30), SizedBox( width: double.infinity, height: 50, child: ElevatedButton(onPressed: _save, child: const Text("保存账单")), ), ], ), ), ); } } // 统计页面 class StatisticPage extends StatefulWidget { const StatisticPage({super.key}); @override State<StatisticPage> createState() => _StatisticPageState(); } class _StatisticPageState extends State<StatisticPage> { double income = 0, expense = 0, balance = 0; bool hasData = false; @override void initState() { super.initState(); _calc(); } Future<void> _calc() async { final prefs = await SharedPreferences.getInstance(); final str = prefs.getString("billList"); if (str == null) { setState(() => hasData = false); return; } List list = jsonDecode(str); double inp = 0, exp = 0; for (var b in list) { if (b["type"] == "收入") { inp += b["money"]; } else { exp += b["money"]; } } setState(() { income = inp; expense = exp; balance = inp - exp; hasData = list.isNotEmpty; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("收支统计")), body: !hasData ? const Center(child: Text("暂无数据可统计")) : Padding( padding: const EdgeInsets.all(16), child: Column( children: [ _buildCard("总收入", income, Colors.green), const SizedBox(height: 16), _buildCard("总支出", expense, Colors.red), const SizedBox(height: 16), _buildCard("当前结余", balance, Colors.teal), ], ), ), ); } Widget _buildCard(String title, double value, Color color) { return Card( color: color.withOpacity(0.05), child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ Text(title, style: TextStyle(fontSize: 18, color: color)), const SizedBox(height: 8), Text( "¥$value", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: color), ), ], ), ), ); } } // 个人中心 class MinePage extends StatelessWidget { const MinePage({super.key}); Future<void> _clearData(BuildContext context) async { final prefs = await SharedPreferences.getInstance(); await prefs.remove("billList"); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("已清空所有数据 ✅"))); } } void _showDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text("确认清空"), content: const Text("确定要清空所有账单?此操作不可恢复!"), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("取消")), TextButton( onPressed: () { _clearData(context); Navigator.pop(ctx); }, child: const Text("确定清空", style: TextStyle(color: Colors.red)), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("个人中心")), body: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ const SizedBox(height: 40), const CircleAvatar(radius: 50, backgroundColor: Colors.teal, child: Icon(Icons.person, color: Colors.white, size: 50)), const SizedBox(height: 20), const Text("极简记账本", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), const SizedBox(height: 40), Card( child: ListTile( leading: const Icon(Icons.delete_forever, color: Colors.red), title: const Text("清空所有账单"), subtitle: const Text("删除后无法恢复"), onTap: () => _showDialog(context), ), ), const SizedBox(height: 20), const Card( child: ListTile( leading: Icon(Icons.info, color: Colors.teal), title: Text("版本信息"), subtitle: Text("v1.0 Flutter + 开源鸿蒙"), ), ), ], ), ), ); } }

📸五、运行效果(最终版)

  1. 底部导航四页面切换流畅,数据全局同步
  2. 新增账单自动保存到shared_preferences
  3. 首页实时展示账单列表
  4. 统计页自动计算收支与结余
  5. 个人中心支持一键清空本地数据
  6. 鸿蒙 / 安卓双端完美运行,无样式错位
  7. 空状态、提示、弹窗交互完整

📝六、Day6 项目优化内容

  1. 全局主题统一:颜色、圆角、卡片风格标准化
  2. IndexedStack 页面缓存:切换页面不重建、不丢失状态
  3. BUG 修复:数据刷新不同步、输入框清空异常
  4. 交互优化:按钮反馈、SnackBar 提示、弹窗确认
  5. 鸿蒙适配:全面屏、状态栏、刘海屏适配
  6. 代码结构优化:模块化、易维护、可扩展

✅七、六天项目完整功能总结

✅ Day1:项目初始化 + 底部导航搭建

✅ Day2:记账页面 + 表单校验

✅ Day3:账单列表 + 本地数据读取

✅ Day4:收支统计 + 数据汇总

✅ Day5:个人中心 + 清空数据功能

✅ Day6:全局优化 + 鸿蒙适配 + 项目完结

📚八、系列推荐

  • Day1:项目初始化 + 底部导航框架(已发布)
  • Day2:记账页面 + 本地数据存储(已发布)
  • Day3:账单列表展示 + 空状态适配(已发布)
  • Day4:收支统计页面 + 数据汇总(已发布)
  • Day5:个人中心 + 清空数据功能(已发布)
  • Day6:项目优化 + 鸿蒙适配 + 完结总结(本篇)

📞九、结束语

经过六天实战,我们从零完成了一款Flutter + 开源鸿蒙 跨端记账本,使用shared_preferences第三方库实现完整持久化功能,代码规范、界面美观、功能完整。

本项目可直接作为:

  • Flutter 入门练手项目
  • 鸿蒙跨平台开发案例

💡 欢迎点赞、收藏、关注,后续持续更新!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/14 18:07:40

终极ncmdump完整指南:3分钟解锁网易云音乐加密文件

终极ncmdump完整指南&#xff1a;3分钟解锁网易云音乐加密文件 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器使用而烦恼吗&#xff1f;ncmdump是一款简单易用的开源工具&#x…

作者头像 李华
网站建设 2026/5/14 18:07:14

AI代理框架实战:从模块化设计到生产部署的工程化指南

1. 项目概述与核心价值最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“ultimate-ai-agents”。光看名字&#xff0c;你可能会觉得又是一个“AI代理”的轮子&#xff0c;毕竟现在市面上各种LangChain、AutoGPT、CrewAI之类的框架已经多如牛毛了。但当我点开这个由stratpo…

作者头像 李华
网站建设 2026/5/14 18:05:34

clwatch:AI编码工具版本监控与变更管理解决方案

1. 项目概述&#xff1a;一个为AI编码工具而生的“版本雷达”如果你和我一样&#xff0c;日常工作中重度依赖Claude Code、Codex CLI这类AI编码工具&#xff0c;那你一定遇到过这个痛点&#xff1a;你永远不知道你用的工具什么时候又悄悄更新了。新版本可能带来了梦寐以求的功能…

作者头像 李华
网站建设 2026/5/14 18:05:04

企业级AI大模型崛起:小白也能掌握的AI时代机遇,速收藏!

本文探讨了企业级AI大模型的市场普及及其影响。随着阿里通义、字节豆包等大模型在金融、政务、制造业等领域的应用&#xff0c;AI正显著提高工作效率&#xff0c;降低人力成本。同时&#xff0c;AI也创造了新的就业岗位&#xff0c;如AI训练、数据标注等。对于程序员和普通工作…

作者头像 李华
网站建设 2026/5/14 18:02:08

三步搞定Windows平台APK安装:告别模拟器卡顿的终极方案

三步搞定Windows平台APK安装&#xff1a;告别模拟器卡顿的终极方案 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 你是否还在为Windows上运行安卓应用而烦恼&#xff…

作者头像 李华