Flutter框架跨平台鸿蒙开发——小票管家APP开发流程
Flutter跨平台鸿蒙应用开发摘要 本文介绍了使用Flutter框架开发跨平台小票管家APP的全流程。该应用具备小票管理、分类统计、金额计算等核心功能,采用Flutter 3.6.2+Dart 3.0.0技术栈,通过sqflite实现本地数据存储。开发过程包括项目初始化、分层架构设计、数据模型定义、数据库服务封装以及UI组件实现。重点展示了小票模型设计、SQLite持久化方案和列表页面交互逻辑,
🚀运行效果展示


Flutter框架跨平台鸿蒙开发——小票管家APP开发流程
📝 前言
随着移动应用开发技术的快速发展,跨平台开发框架已成为移动开发领域的重要趋势。Flutter作为Google推出的开源UI工具包,凭借其"一次编写,多平台运行"的特性,在跨平台开发领域占据了重要地位。本文将详细介绍如何使用Flutter框架开发一款跨平台的小票管家APP,并成功运行在鸿蒙系统上。
📱 应用介绍
功能概述
小票管家APP是一款用于管理个人消费小票的移动应用,主要功能包括:
- 📋 小票列表管理:展示所有小票,支持下拉刷新和左滑删除
- ➕ 添加小票:通过表单输入小票信息
- 📊 小票详情:查看小票的详细信息
- 🏷️ 分类管理:支持多种小票分类
- 💵 金额统计:实时显示总消费金额
技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Flutter | 3.6.2+ | 跨平台UI框架 |
| Dart | 3.0.0+ | 开发语言 |
| sqflite | 2.4.1 | 本地数据库 |
| path_provider | 2.1.5 | 文件路径管理 |
| intl | 0.19.0 | 国际化和格式化 |
| flutter_slidable | 3.1.2 | 滑动操作组件 |
🏗️ 开发流程
1. 项目初始化
首先,我们需要创建一个Flutter项目,并添加必要的依赖。
# 创建Flutter项目
flutter create flutter_text
# 添加依赖
flutter pub add sqflite path_provider intl flutter_slidable
2. 项目结构设计
遵循Flutter最佳实践,我们采用清晰的分层架构:
lib/
├── models/ # 数据模型
│ └── receipt.dart
├── services/ # 业务逻辑和数据持久化
│ └── receipt_service.dart
├── screens/ # 页面组件
│ ├── receipt_list_screen.dart
│ ├── add_receipt_screen.dart
│ └── receipt_detail_screen.dart
└── main.dart # 应用入口
3. 核心功能实现
3.1 数据模型设计
首先,我们定义小票的数据模型:
/// 小票数据模型
class Receipt {
/// 小票唯一标识符
final String id;
/// 商家名称
final String merchantName;
/// 消费金额
final double amount;
/// 消费日期
final DateTime date;
/// 小票分类
final String category;
/// 小票描述
final String description;
/// 小票图片路径 (可选)
final String? imagePath;
/// 创建小票实例
Receipt({
String? id,
required this.merchantName,
required this.amount,
DateTime? date,
required this.category,
required this.description,
this.imagePath,
}) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(),
date = date ?? DateTime.now();
/// 从Map转换为Receipt实例
factory Receipt.fromMap(Map<String, dynamic> map) {
return Receipt(
id: map['id'],
merchantName: map['merchantName'],
amount: map['amount'],
date: DateTime.parse(map['date']),
category: map['category'],
description: map['description'],
imagePath: map['imagePath'],
);
}
/// 转换为Map,用于数据持久化
Map<String, dynamic> toMap() {
return {
'id': id,
'merchantName': merchantName,
'amount': amount,
'date': date.toIso8601String(),
'category': category,
'description': description,
'imagePath': imagePath,
};
}
}
3.2 数据持久化服务
接下来,我们实现小票数据的持久化服务:
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import '../models/receipt.dart';
/// 小票数据持久化服务
class ReceiptService {
static final ReceiptService _instance = ReceiptService._internal();
factory ReceiptService() => _instance;
ReceiptService._internal();
static Database? _database;
/// 获取数据库实例
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
/// 初始化数据库
Future<Database> _initDatabase() async {
String databasesPath = await getDatabasesPath();
String path = '$databasesPath/receipts.db';
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
/// 创建数据库表
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE receipts (
id TEXT PRIMARY KEY,
merchantName TEXT NOT NULL,
amount REAL NOT NULL,
date TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT NOT NULL,
imagePath TEXT
)
''');
}
/// 添加小票
Future<void> addReceipt(Receipt receipt) async {
final db = await database;
await db.insert('receipts', receipt.toMap());
}
/// 获取所有小票
Future<List<Receipt>> getAllReceipts() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('receipts', orderBy: 'date DESC');
return List.generate(maps.length, (i) {
return Receipt.fromMap(maps[i]);
});
}
/// 其他CRUD方法...
}
3.3 页面实现
3.3.1 小票列表页面
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:intl/intl.dart';
import '../models/receipt.dart';
import '../services/receipt_service.dart';
/// 小票列表页面
class ReceiptListScreen extends StatefulWidget {
const ReceiptListScreen({super.key});
State<ReceiptListScreen> createState() => _ReceiptListScreenState();
}
class _ReceiptListScreenState extends State<ReceiptListScreen> {
final ReceiptService _receiptService = ReceiptService();
List<Receipt> _receipts = [];
bool _isLoading = true;
double _totalAmount = 0.0;
static final DateFormat _dateFormat = DateFormat('yyyy-MM-dd HH:mm');
static final NumberFormat _currencyFormat = NumberFormat.currency(locale: 'zh_CN', symbol: '¥');
void initState() {
super.initState();
_loadReceipts();
}
/// 加载小票数据
Future<void> _loadReceipts() async {
setState(() {
_isLoading = true;
});
try {
final receipts = await _receiptService.getAllReceipts();
final totalAmount = await _receiptService.getTotalAmount();
setState(() {
_receipts = receipts;
_totalAmount = totalAmount;
});
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('加载小票数据失败: $e')),
);
}
} finally {
setState(() {
_isLoading = false;
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('小票管家'),
actions: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Text(
'总计: ${_currencyFormat.format(_totalAmount)}',
style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
),
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _loadReceipts,
child: _receipts.isEmpty
? const Center(child: Text('暂无小票数据'))
: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: _receipts.length,
itemBuilder: (context, index) {
final receipt = _receipts[index];
return Slidable(
key: ValueKey(receipt.id),
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
onPressed: (context) => _deleteReceipt(receipt.id),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: '删除',
),
],
),
child: Card(
elevation: 2.0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Container(
width: 50.0,
height: 50.0,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.0),
),
child: const Icon(Icons.receipt_long, color: Colors.white),
),
title: Text(receipt.merchantName),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${receipt.category} • ${_dateFormat.format(receipt.date)}'),
if (receipt.description.isNotEmpty) ...[
const SizedBox(height: 4.0),
Text(receipt.description, overflow: TextOverflow.ellipsis),
],
],
),
trailing: Text(
_currencyFormat.format(receipt.amount),
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
),
onTap: () => _navigateToReceiptDetail(receipt),
),
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToAddReceipt,
tooltip: '添加小票',
child: const Icon(Icons.add),
),
);
}
/// 其他方法...
}
3.3.2 添加小票页面
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/receipt.dart';
import '../services/receipt_service.dart';
/// 添加小票页面
class AddReceiptScreen extends StatefulWidget {
const AddReceiptScreen({super.key});
State<AddReceiptScreen> createState() => _AddReceiptScreenState();
}
class _AddReceiptScreenState extends State<AddReceiptScreen> {
final ReceiptService _receiptService = ReceiptService();
final _formKey = GlobalKey<FormState>();
String _merchantName = '';
double _amount = 0.0;
DateTime _date = DateTime.now();
String _category = '餐饮';
String _description = '';
final List<String> _categories = [
'餐饮', '购物', '交通', '娱乐', '医疗', '教育', '其他',
];
final DateFormat _dateFormat = DateFormat('yyyy-MM-dd HH:mm');
/// 提交表单,添加小票
Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
final receipt = Receipt(
merchantName: _merchantName,
amount: _amount,
date: _date,
category: _category,
description: _description,
);
try {
await _receiptService.addReceipt(receipt);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('小票添加成功')),
);
Navigator.pop(context);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('添加小票失败')),
);
}
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('添加小票')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 商家名称字段
TextFormField(
decoration: const InputDecoration(
labelText: '商家名称',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.store),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入商家名称';
}
return null;
},
onSaved: (value) {
_merchantName = value!;
},
),
const SizedBox(height: 16.0),
/// 消费金额字段
TextFormField(
decoration: const InputDecoration(
labelText: '消费金额',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入消费金额';
}
final double? amount = double.tryParse(value);
if (amount == null || amount <= 0) {
return '请输入有效的消费金额';
}
return null;
},
onSaved: (value) {
_amount = double.parse(value!);
},
),
const SizedBox(height: 16.0),
/// 其他表单字段...
/// 提交按钮
SizedBox(
width: double.infinity,
height: 50.0,
child: ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
child: const Text('保存小票', style: TextStyle(fontSize: 18.0)),
),
),
],
),
),
),
);
}
/// 其他方法...
}
3.3 页面导航
最后,我们实现应用的主入口和页面导航:
import 'package:flutter/material.dart';
import 'screens/receipt_list_screen.dart';
/// 小票管家APP主入口
void main() {
runApp(const ReceiptManagerApp());
}
/// 小票管家APP
class ReceiptManagerApp extends StatelessWidget {
/// 创建小票管家APP实例
const ReceiptManagerApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '小票管家',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const ReceiptListScreen(),
debugShowCheckedModeBanner: false,
);
}
}
🚀 鸿蒙平台适配
1. 环境准备
在进行鸿蒙开发前,我们需要安装必要的工具:
- DevEco Studio:鸿蒙应用开发IDE
- Flutter SDK:Flutter开发工具包
- HUAWEI DevEco Device Tool:设备调试工具
2. 配置鸿蒙支持
Flutter 3.0+ 已原生支持鸿蒙系统,我们只需在项目中添加鸿蒙平台配置:
# 初始化鸿蒙模块
flutter create --platforms=ohos .
3. 构建鸿蒙应用
使用以下命令构建鸿蒙应用:
# 构建鸿蒙应用
flutter build ohos
# 运行鸿蒙应用
flutter run -d ohos
📊 应用架构流程图
🧪 测试与调试
1. 单元测试
我们可以使用Flutter的测试框架进行单元测试:
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_text/models/receipt.dart';
void main() {
test('Receipt model should be created correctly', () {
final receipt = Receipt(
merchantName: 'Test Merchant',
amount: 100.0,
category: '餐饮',
description: 'Test Description',
);
expect(receipt.merchantName, 'Test Merchant');
expect(receipt.amount, 100.0);
expect(receipt.category, '餐饮');
expect(receipt.description, 'Test Description');
});
test('Receipt should be convertible to Map', () {
final receipt = Receipt(
merchantName: 'Test Merchant',
amount: 100.0,
category: '餐饮',
description: 'Test Description',
);
final map = receipt.toMap();
expect(map['merchantName'], 'Test Merchant');
expect(map['amount'], 100.0);
expect(map['category'], '餐饮');
expect(map['description'], 'Test Description');
});
}
2. 集成测试
对于集成测试,我们可以使用Flutter的集成测试框架:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_text/main.dart';
void main() {
testWidgets('Add receipt test', (WidgetTester tester) async {
// 启动应用
await tester.pumpWidget(const ReceiptManagerApp());
// 点击添加按钮
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();
// 填写表单
await tester.enterText(find.widgetWithText(TextFormField, '商家名称'), '测试商家');
await tester.enterText(find.widgetWithText(TextFormField, '消费金额'), '50.0');
await tester.tap(find.text('保存小票'));
await tester.pumpAndSettle();
// 验证小票是否添加成功
expect(find.text('测试商家'), findsOneWidget);
expect(find.text('¥50.00'), findsOneWidget);
});
}
📝 总结
通过本文的介绍,我们详细了解了如何使用Flutter框架开发一款跨平台的小票管家APP,并成功适配鸿蒙系统。主要实现了以下功能:
- 数据模型设计:定义了小票的基本属性和方法
- 数据持久化:使用SQLite数据库存储小票数据
- UI界面实现:包含小票列表、添加小票和小票详情页面
- 鸿蒙平台适配:实现了Flutter应用在鸿蒙系统上的运行
Flutter框架的跨平台特性使得我们可以只编写一套代码,就能在包括鸿蒙在内的多个平台上运行。这大大提高了开发效率,降低了维护成本。
未来,我们可以进一步优化应用,添加更多功能:
- 🔍 小票搜索:支持按商家名称、日期范围搜索
- 📊 消费统计:提供消费趋势分析和图表展示
- ☁️ 云同步:支持多设备数据同步
- 📸 OCR识别:支持扫描小票自动识别信息
通过不断优化和扩展,小票管家APP可以更好地满足用户的需求,成为一款功能强大、易用的个人消费管理工具。
📚 参考资料
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)