基于Flutter开发个人记账应用的入门指南

云信安装大师
90
AI 质量分
22 4 月, 2025
6 分钟阅读
0 阅读

基于Flutter开发个人记账应用的入门指南

引言

Flutter是Google推出的跨平台移动应用开发框架,使用Dart语言编写,可以快速构建iOS和Android应用。本文将带你从零开始,使用Flutter开发一个简单的个人记账应用。通过这个项目,你将学习到Flutter的基础知识、状态管理和本地数据存储等核心概念。

准备工作

在开始之前,请确保你的开发环境已经满足以下要求:

  1. 安装Flutter SDK

    代码片段
    # 下载Flutter SDK
    git clone https://github.com/flutter/flutter.git -b stable
    
    # 添加Flutter到环境变量
    export PATH="$PATH:`pwd`/flutter/bin"
    
    # 运行医生检查工具
    flutter doctor
    
  2. 安装IDE

    • Android Studio (推荐)
    • VS Code (轻量级选择)
  3. 基础知识

    • 基本的Dart语法
    • Widget概念理解

项目创建与初始化

  1. 创建新项目

    代码片段
    flutter create my_money_tracker
    cd my_money_tracker
    
  2. 添加依赖
    修改pubspec.yaml文件,添加以下依赖:

    代码片段
    dependencies:
      flutter:
        sdk: flutter
      provider: ^6.0.5    # 状态管理
      intl: ^0.18.1       # 日期格式化
      shared_preferences: ^2.2.0 # 本地存储
      path_provider: ^2.0.14    # 文件路径获取
      hive: ^2.2.3        # NoSQL数据库
      hive_flutter: ^1.1.0 
    

    然后运行:

    代码片段
    flutter pub get
    

应用架构设计

我们的记账应用将包含以下主要功能:
– 记录收入和支出
– 分类管理交易记录
– 查看历史记录和统计

数据模型设计

首先定义我们的核心数据模型。在lib/models目录下创建transaction.dart

代码片段
import 'package:hive/hive.dart';

part 'transaction.g.dart'; // Hive生成的代码文件

@HiveType(typeId: 0)
class Transaction {
  @HiveField(0)
  final String id;

  @HiveField(1)
  final String title;

  @HiveField(2)
  final double amount;

  @HiveField(3)
  final DateTime date;

  @HiveField(4)
  final bool isExpense; // true表示支出,false表示收入

  Transaction({
    required this.id,
    required this.title,
    required this.amount,
    required this.date,
    required this.isExpense,
  });
}

运行以下命令生成Hive适配器:

代码片段
flutter packages pub run build_runner build --delete-conflicting-outputs

UI结构设计

我们将使用Material Design风格构建UI:

代码片段
main.dart (入口文件)
├── screens/
│   ├── home_screen.dart      // 主页面 
│   ├── add_transaction.dart // 添加交易页面 
│   └── stats_screen.dart    //统计页面 
├── widgets/                //可复用组件 
│   ├── transaction_list.dart 
│   └── chart.dart 
└── providers/              //状态管理 
    └── transactions.dart 

实现核心功能

Home Screen实现

lib/screens/home_screen.dart中:

代码片段
import 'package:flutter/material.dart';
import 'package:my_money_tracker/widgets/transaction_list.dart';
import 'package:my_money_tracker/widgets/chart.dart';
import 'package:my_money_tracker/screens/add_transaction.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {

 bool _showChart = false;

 @override
 Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我的记账本'),
        actions: [
          IconButton(
            icon: Icon(_showChart ? Icons.list : Icons.pie_chart),
            onPressed: () {
              setState(() {
                _showChart = !_showChart;
              });
            },
          ),
        ],
      ),
      body: Column(
        children: [
          if (_showChart) Chart(),
          if (!_showChart) TransactionList(),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () => Navigator.push(
          context, 
          MaterialPageRoute(builder: (_) => AddTransactionScreen())
        ),
      ),
    );
 }
}

Add Transaction Screen实现

lib/screens/add_transaction.dart中:

代码片段
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:my_money_tracker/models/transaction.dart';
import 'package:provider/provider.dart';

class AddTransactionScreen extends StatefulWidget {
 @override
 _AddTransactionScreenState createState() => _AddTransactionScreenState();
}

class _AddTransactionScreenState extends State<AddTransactionScreen> {
 final _titleController = TextEditingController();
 final _amountController = TextEditingController();
 DateTime? _selectedDate;
 bool _isExpense = true;

 void _submitData() {
    if (_amountController.text.isEmpty || 
        double.parse(_amountController.text) <=0 ||
        _titleController.text.isEmpty ||
        _selectedDate == null) return;

    Provider.of<Transactions>(context, listen: false).addTransaction(
      Transaction(
        id: DateTime.now().toString(),
        title: _titleController.text,
        amount: double.parse(_amountController.text),
        date: _selectedDate!,
        isExpense: _isExpense,
      ),
    );

    Navigator.of(context).pop();
 }

 void _presentDatePicker() {
    showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2023),
      lastDate: DateTime.now(),
    ).then((pickedDate) {
      if (pickedDate == null) return;
      setState(() {
        _selectedDate = pickedDate;
      });
    });
 }

 @override
 Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('添加交易')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child Column(
          children [
            TextField(
              decoration InputDecoration(labelText '标题'),
              controller _titleController,
              onSubmitted (_) =>_submitData(),
            ),
            TextField(
              decoration InputDecoration(labelText '金额'),
              controller_amountController,
              keyboardType TextInputType.numberWithOptions(decimal true),
              onSubmitted (_) =>_submitData(),
            ),
            Row(
              children [
                Expanded(child Text(_selectedDate == null 
                  ?'未选择日期'
                  :'日期 ${DateFormat.yMd().format(_selectedDate!)}'
                )),
                TextButton.icon(
                  icon Icon(Icons.calendar_today),
                  label Text('选择日期'),
                  onPressed_presentDatePicker,
                )
              ],
            ),
            SwitchListTile(
              title Text('支出/收入'),
              subtitle Text(_isExpense ?'支出' :'收入'),
              value_isExpense,
              onChanged (value) =>setState(() {_isExpense = value;}),
            ),
            SizedBox(height20),
            ElevatedButton.icon(
              icon Icon(Icons.save),
              label Text('保存'),
              onPressed_submitData,
            )
          ],
        )
      )
    );
 }
}

State Management with Provider

lib/providers/transactions.dart中:

代码片段
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import '../models/transaction.dart';

class Transactions with ChangeNotifier {
 List<Transaction>_items = [];

 List<Transaction>get items [..._items];

 Future<void>loadTransactions() async {
    final box = await Hive.openBox<Transaction>('transactions');

_items = box.values.toList();
notifyListeners();
 }

 Future<void>addTransaction(Transaction tx) async {
final box = await Hive.openBox<Transaction>('transactions');
await box.add(tx);
_items.add(tx);
notifyListeners();
 }

 Future<void>removeTransaction(String id) async {
final box = await Hive.openBox<Transaction>('transactions');
final index=_items.indexWhere((tx)=>tx.id == id);
if(index >=0){
await box.deleteAt(index);
_items.removeAt(index);
notifyListeners();
}
 }
}

Hive数据库初始化

lib/main.dart中初始化Hive:

代码片段
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import './models/transaction.dart';
import './providers/transactions.dart'; 

void main() async {  
//初始化Hive  
await Hive.initFlutter();  

//注册适配器  
Hive.registerAdapter(TransactionAdapter());  

//打开box  
await Hive.openBox<Transaction>('transactions');  

runApp(MyApp());  
}  

class MyApp extends StatelessWidget {  
@override  
Widget build(BuildContext context) {  
return ChangeNotifierProvider(  
create:(ctx)=>Transactions(),  
child MaterialApp(  
title:'个人记账',  
theme ThemeData(primarySwatch Colors.purple),  
home HomeScreen(),  
),);}}}

Transaction List Widget实现

lib/widgets/transaction_list.dart中:

代码片段
import 'package:flutter/material dart'; 
import '../models transaction dart'; 

class TransactionList extends StatelessWidget {  

@override  
Widget build(BuildContext context){  

final transactions=Provider.of<Transactions>(context);  

return ListView.builder(  

itemCount transactions.items.length,  

itemBuilder (ctx,index){  

final tx=transactions.items[index];  

return Dismissible(  

key ValueKey(tx.id),  

background Container(color Colors.red),  

direction DismissDirection.endToStart,  

onDismissed (direction){  

transactions.removeTransaction(tx.id);},  

child Card(child ListTile(leading CircleAvatar(child FittedBox(child Text('\$${tx.amount.toStringAsFixed(2)}'))),title Text(tx.title),subtitle Text(DateFormat.yMMMd().format(tx.date)),trailing Icon(tx.isExpense?Icons.money_off:Icons.monetization_on,color tx.isExpense?Colors.red Colors.green))));});}}

Chart Widget实现(简单统计)

lib/widgets/chart dart中:

代码片段

//省略导入语句...

class Chart extends StatelessWidget {  

@override Widget build(BuildContext context){  

final transactions=Provider.of<Transactions>(context); Map<String double>groupedTxValues={}; for(var i=0;i<7;i++){final weekDay=DateTime.now().subtract(Duration(days i));var totalSum=0; var dayName=DateFormat.E().format(weekDay).substring(0,1); for(var j=0;j<transactions.items.length;j++){if(transactions.items[j].date.day==weekDay.day&&transactions.items[j].date.month==weekDay.month&&transactions.items[j].date.year==weekDay.year){totalSum+=transactions.items[j].amount.toInt();}}groupedTxValues.putIfAbsent(dayName()=>totalSum.toDouble());}return Card(elevation6,margin EdgeInsets.all20,child Padding(padding EdgeInsets.all10,child Row(mainAxisAlignment MainAxisAlignment spaceAround children groupedTxValues.map((data){return Flexible(fit FlexFit.tight child ChartBar(data.key data.value groupedTxValues.values.reduce((value element)=>value+element)==00?01 data.value groupedTxValues.values.reduce((value element)=>value+element)));}).toList()))));}}

class ChartBar extends StatelessWidget { final String label; final double value; final double percentage; ChartBar(this.label this.value this percentage); @override Widget build(BuildContext context){ return Column(mainAxisSize MainAxisSize.min mainAxisAlignment MainAxisAlignment center children [FittedBox(child Text('\$${value.toStringAsFixed()}') SizedBox(height4 Container(height60 width10 decoration BoxDecoration(border Border.all(color Colors grey width10 borderRadius BorderRadius circular10 child FractionallySizedBox(heightFactor percentage alignment Alignment bottomCenter child Container(color Theme.of(context).primaryColor))) SizedBox(height4 Text(label)]);}}

测试与调试

1.热重载:保存更改后按r键快速查看效果

2.常见问题解决

-如果遇到”HIVE未初始化”错误确保main函数标记为async并在runApp前完成初始化

-UI不更新检查是否忘记调用notifyListeners()

-数据不持久确保正确await所有异步操作

总结

通过本教程我们完成了:

✅ Flutter项目创建与配置

✅ Hive本地数据库集成

✅ Provider状态管理实践

✅ Material Design UI构建

✅完整的CRUD功能实现

下一步改进方向:

🔹添加用户认证(Firebase Auth)

🔹实现多账户支持

🔹增加数据备份到云端功能

🔹更丰富的统计图表和分析功能

原创 高质量