OpenHarmony 与 Flutter 的完美融合:打造现代化个人财务管理应用(1.0.0版本)
摘要:Webview崩溃会导致白屏,影响用户体验。本文介绍在ArkTS中通过onRenderExited监听Webview崩溃事件,实现自动恢复和日志上报。核心方案包括:1)检测崩溃原因(如OOM或底层Bug);2)自动重载页面并防抖处理,避免死循环;3)通过环形缓冲区记录崩溃前的JS日志辅助分析。该机制将系统级故障降级为短暂页面刷新,提升应用健壮性。
·
目录
简介
什么是 OpenHarmony?
OpenHarmony 是一个开放的、去中心化的金融生态系统,旨在为用户提供透明、安全且高效的财务管理解决方案。它强调用户对自己资产的完全控制权,并通过智能合约和区块链技术确保交易的安全性。
为什么选择 Flutter?
Flutter 是 Google 推出的跨平台 UI 框架,具有以下优势:
- 高性能:使用 Dart 语言,编译为原生代码
- 跨平台:一套代码可运行于 iOS、Android、Web 等多个平台
- 丰富的组件库:Material Design 和 Cupertino 风格组件
- 热重载:快速迭代开发体验
- 活跃社区:大量第三方包和最佳实践
本文目标
本文将详细介绍如何使用 Flutter 构建一个功能完整的 OpenHarmony 钱包应用。
核心概念
1. 数据模型
交易模型(Transaction)
class Transaction {
final String id; // 唯一标识符
final String title; // 交易说明
final double amount; // 交易金额
final DateTime date; // 交易时间
final TransactionType type; // 交易类型(收入/支出)
final String category; // 分类标签
Transaction({
required this.id,
required this.title,
required this.amount,
required this.date,
required this.type,
required this.category,
});
}
说明:
id:使用时间戳确保唯一性,便于快速查询和删除title:用户输入的交易描述,帮助回忆交易内容amount:使用double类型存储金额,支持小数点date:记录交易发生的精确时间type:通过枚举区分收入和支出,类型安全category:便于后续的分类统计和分析
交易类型枚举
enum TransactionType { income, expense }
说明:使用枚举而不是字符串,可以避免拼写错误,提高代码的类型安全性。
项目架构
整体结构
lib/
├── main.dart # 应用入口
├── openharmony_wallet_widget.dart # OpenHarmony 钱包组件
└── models/ # 数据模型(可选)
架构设计原则
- 单一职责原则:每个组件只负责一个功能
- 组件化:将 UI 拆分为可复用的小组件
- 状态管理:使用
StatefulWidget管理钱包状态 - 数据驱动:UI 由数据驱动,数据变化自动更新 UI
组件详解
OpenHarmonyWallet 组件
这是整个应用的核心组件,继承自 StatefulWidget,用于管理钱包的所有状态和交互。
组件定义
class OpenHarmonyWallet extends StatefulWidget {
final double initialBalance;
final List<Transaction> transactions;
final Function(Transaction)? onTransactionTap;
const OpenMoneyWallet({
Key? key,
this.initialBalance = 0.0,
this.transactions = const [],
this.onTransactionTap,
}) : super(key: key);
State<OpenHarmonyWallet> createState() => _OpenHarmonyWalletState();
}
参数说明:
initialBalance:初始余额,默认为 0.0transactions:初始交易列表,默认为空列表onTransactionTap:交易添加时的回调函数
状态管理
class _OpenHarmonyWalletState extends State<OpenHarmonyWallet> {
late double _currentBalance;
late List<Transaction> _transactions;
void initState() {
super.initState();
_currentBalance = widget.initialBalance;
_transactions = List.from(widget.transactions);
_calculateBalance();
}
说明:
_currentBalance:实时余额,使用late关键字延迟初始化_transactions:本地交易列表副本,避免直接修改 widget 参数initState:在组件初始化时计算初始余额
核心方法
1. 余额计算
void _calculateBalance() {
double balance = widget.initialBalance;
for (var transaction in _transactions) {
if (transaction.type == TransactionType.income) {
balance += transaction.amount;
} else {
balance -= transaction.amount;
}
}
setState(() {
_currentBalance = balance;
});
}
工作流程:
- 从初始余额开始
- 遍历所有交易记录
- 根据交易类型(收入/支出)更新余额
- 调用
setState()触发 UI 重新构建
2. 添加交易
void addTransaction(Transaction transaction) {
setState(() {
_transactions.insert(0, transaction);
_calculateBalance();
});
}
说明:
insert(0, ...)将新交易插入列表头部,最新交易显示在最上面- 自动重新计算余额
- 触发 UI 更新
3. 删除交易
void removeTransaction(String transactionId) {
setState(() {
_transactions.removeWhere((t) => t.id == transactionId);
_calculateBalance();
});
}
4. 分类统计
Map<String, double> getCategoryStats() {
Map<String, double> stats = {};
for (var transaction in _transactions) {
if (transaction.type == TransactionType.expense) {
stats[transaction.category] =
(stats[transaction.category] ?? 0) + transaction.amount;
}
}
return stats;
}
工作原理:
- 只统计支出交易
- 按分类汇总金额
- 使用
??操作符处理首次出现的分类
实现细节
UI 构建
1. 余额卡片
Widget _buildBalanceCard() {
return Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade400, Colors.blue.shade800],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'OpenHarmony 钱包',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.white70,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
Text(
'¥${_currentBalance.toStringAsFixed(2)}',
style: Theme.of(context).textTheme.displayMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
设计特点:
- 渐变背景:从浅蓝到深蓝,视觉效果现代
- 圆角:
borderRadius: BorderRadius.circular(20)提供柔和边界 - 阴影效果:增加深度感和立体感
- 响应式文本:使用
Theme.of(context)获取主题文本样式

2. 快速操作按钮
Widget _buildActionButtons() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildActionButton(
icon: Icons.arrow_downward,
label: '收入',
color: Colors.green,
onTap: () => _showTransactionDialog(TransactionType.income),
),
_buildActionButton(
icon: Icons.arrow_upward,
label: '支出',
color: Colors.red,
onTap: () => _showTransactionDialog(TransactionType.expense),
),
_buildActionButton(
icon: Icons.pie_chart,
label: '统计',
color: Colors.orange,
onTap: _showStatisticsDialog,
),
],
),
);
}
说明:
- 三个主要操作:添加收入、添加支出、查看统计
- 使用不同颜色区分不同操作
- 图标清晰直观

3. 单个操作按钮
Widget _buildActionButton({
required IconData icon,
required String label,
required Color color,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
],
),
);
}
设计亮点:
GestureDetector处理点击事件- 半透明背景色
color.withOpacity(0.1)创建视觉层次 - 固定尺寸 56x56 遵循 Material Design 规范
4. 交易历史列表
Widget _buildTransactionHistory() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'交易历史',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 12),
if (_transactions.isEmpty)
Padding(
padding: const EdgeInsets.all(32),
child: Center(
child: Text(
'暂无交易记录',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey,
),
),
),
)
else
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _transactions.length,
itemBuilder: (context, index) {
return _buildTransactionItem(_transactions[index]);
},
),
],
);
}
关键点:
- 使用
ListView.builder高效渲染大列表 shrinkWrap: true让列表适应内容高度NeverScrollableScrollPhysics禁用列表自身滚动
5. 单个交易项
Widget _buildTransactionItem(Transaction transaction) {
final isIncome = transaction.type == TransactionType.income;
final color = isIncome ? Colors.green : Colors.red;
final sign = isIncome ? '+' : '-';
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
isIncome ? Icons.arrow_downward : Icons.arrow_upward,
color: color,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
transaction.title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
Text(
transaction.category,
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'$sign¥${transaction.amount.toStringAsFixed(2)}',
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
Text(
_formatDate(transaction.date),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 11,
),
),
],
),
const SizedBox(width: 8),
GestureDetector(
onTap: () => removeTransaction(transaction.id),
child: Icon(
Icons.close,
color: Colors.grey.shade400,
size: 18,
),
),
],
),
);
}
布局分析:
- 左侧图标:直观显示收入/支出
- 中间内容:交易标题和分类
- 右侧金额:显示金额和时间
- 删除按钮:快速删除交易

交互功能
1. 添加交易对话框
void _showTransactionDialog(TransactionType type) {
final titleController = TextEditingController();
final amountController = TextEditingController();
String selectedCategory = '其他';
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(type == TransactionType.income ? '添加收入' : '添加支出'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: '交易说明',
hintText: '请输入交易说明',
),
),
const SizedBox(height: 12),
TextField(
controller: amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: '金额',
hintText: '请输入金额',
prefixText: '¥',
),
),
const SizedBox(height: 12),
DropdownButton<String>(
value: selectedCategory,
isExpanded: true,
items: _getCategoryList(type)
.map((cat) => DropdownMenuItem(
value: cat,
child: Text(cat),
))
.toList(),
onChanged: (value) {
if (value != null) {
selectedCategory = value;
}
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
if (titleController.text.isNotEmpty &&
amountController.text.isNotEmpty) {
final transaction = Transaction(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: titleController.text,
amount: double.parse(amountController.text),
date: DateTime.now(),
type: type,
category: selectedCategory,
);
addTransaction(transaction);
widget.onTransactionTap?.call(transaction);
Navigator.pop(context);
}
},
child: const Text('确认'),
),
],
),
);
}
工作流程:
- 初始化文本控制器和分类变量
- 显示模态对话框
- 用户输入交易说明、金额和分类
- 验证必填字段不为空
- 创建 Transaction 对象
- 调用
addTransaction()并触发回调 - 关闭对话框

2. 统计对话框
void _showStatisticsDialog() {
final stats = getCategoryStats();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('支出统计'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: stats.entries
.map((entry) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Text(
'¥${entry.value.toStringAsFixed(2)}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
))
.toList(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
],
),
);
}
说明:
- 调用
getCategoryStats()获取分类统计数据 - 使用
map()将统计数据转换为 UI 组件 - 显示分类和对应的总支出
在这里插入图片描述

使用示例
在主应用中集成 OpenMoneyWallet
import 'package:flutter/material.dart';
import 'openmoney_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'OpenMoney 钱包',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OpenHarmony 钱包'),
elevation: 0,
),
body: OpenHarmonyWallet(
initialBalance: 5000.0,
transactions: [
Transaction(
id: '1',
title: '月薪',
amount: 5000.0,
date: DateTime.now().subtract(const Duration(days: 5)),
type: TransactionType.income,
category: '工资',
),
Transaction(
id: '2',
title: '午餐',
amount: 35.0,
date: DateTime.now().subtract(const Duration(hours: 2)),
type: TransactionType.expense,
category: '食物',
),
],
onTransactionTap: (transaction) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('添加交易: ${transaction.title}')),
);
},
),
);
}
}
说明:
- 初始余额设置为 5000 元
- 预加载两条示例交易
- 通过
onTransactionTap回调处理交易添加事件
最佳实践
1. 状态管理
问题:随着应用复杂度增加,状态管理变得困难
解决方案:使用 Provider 包进行全局状态管理
class WalletProvider extends ChangeNotifier {
double _balance = 0.0;
List<Transaction> _transactions = [];
void addTransaction(Transaction transaction) {
_transactions.add(transaction);
_updateBalance();
notifyListeners();
}
void _updateBalance() {
// 计算逻辑
}
}
2. 数据持久化
问题:应用关闭后数据丢失
解决方案:使用 shared_preferences 或 hive
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveBalance(double balance) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble('balance', balance);
}
Future<double> loadBalance() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getDouble('balance') ?? 0.0;
}
3. 错误处理
问题:金额输入可能无效
解决方案:
try {
final amount = double.parse(amountController.text);
if (amount <= 0) {
throw FormatException('金额必须大于 0');
}
} on FormatException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('输入错误: ${e.message}')),
);
}
4. 性能优化
问题:大量交易记录导致列表卡顿
解决方案:使用分页加载
class PaginatedTransactionList {
static const pageSize = 20;
int _currentPage = 0;
List<Transaction> getNextPage(List<Transaction> allTransactions) {
final start = _currentPage * pageSize;
final end = (start + pageSize).clamp(0, allTransactions.length);
_currentPage++;
return allTransactions.sublist(start, end);
}
}
5. 安全性考虑
问题:敏感财务数据需要保护
解决方案:使用加密存储
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
const storage = FlutterSecureStorage();
Future<void> saveEncryptedBalance(double balance) async {
await storage.write(
key: 'balance',
value: balance.toString(),
);
}
总结
核心要点
- 数据模型:清晰的数据结构是应用的基础
- 组件化:将 UI 拆分为可复用的小组件
- 状态管理:使用
setState()或 Provider 管理状态 - 用户交互:通过对话框和按钮提供直观的交互
- 数据分析:支持分类统计和数据可视化
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)