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

前言

本地记账APP是日常高频使用的工具类应用,本文基于 Flutter + 鸿蒙6.0(API20+) 环境,以本地记账APP为实战载体,详细讲解如何集成适配鸿蒙系统的三方库,实现账单添加、收支分类、余额统计、数据持久化等完整功能,代码全程带注释,可直接复制运行,助力开发者快速掌握Flutter跨端开发鸿蒙应用的核心流程。

一、开发环境准备

1.1 基础环境配置

  • Flutter SDK:3.16.0 及以上版本
  • DevEco Studio:5.0.1 及以上版本
  • 鸿蒙 SDK:API20(HarmonyOS 6.0)
  • 开发编辑器:VS Code / Android Studio
  • 鸿蒙真机/模拟器(API20+)

1.2 Flutter鸿蒙环境启用

执行以下命令,开启Flutter对鸿蒙平台的支持,并验证环境是否正常:

# 启用鸿蒙平台支持
flutter config --enable-openharmony
# 验证环境(确保OpenHarmony相关无报错)
flutter doctor

二、项目创建与鸿蒙基础配置

2.1 创建Flutter项目

通过命令行创建支持鸿蒙平台的Flutter项目,命名为本地记账APP相关名称:

# 创建项目
flutter create flutter_harmony_account
# 进入项目目录
cd flutter_harmony_account

2.2 鸿蒙SDK版本适配

修改项目中 openharmony/app/build.gradle 文件,配置鸿蒙API20相关参数,确保适配鸿蒙6.0系统:

openharmony {
    compileSdkVersion 20 // 鸿蒙6.0对应API20
    defaultConfig {
        minSdkVersion 20 // 最低支持鸿蒙6.0
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"
    }
}

三、鸿蒙兼容三方库集成

本次记账APP选用3个稳定兼容鸿蒙6.0的常用三方库,覆盖数据持久化、消息提示、时间/金额格式化核心需求,具体如下:

三方库名称 推荐版本 核心功能
shared_preferences ^2.2.2 本地数据持久化,保存账单记录(鸿蒙兼容)
fluttertoast ^8.2.8 鸿蒙平台轻量级消息提示(适配鸿蒙系统弹窗)
intl ^0.18.1 时间格式化、金额保留两位小数,统一展示格式

3.1 添加依赖并安装

打开项目根目录的 pubspec.yaml 文件,在 dependencies 节点下添加三方库依赖:

dependencies:
  flutter:
    sdk: flutter
  # 本地数据持久化(保存账单)
  shared_preferences: ^2.2.2
  # 鸿蒙消息提示
  fluttertoast: ^8.2.8
  # 时间、金额格式化
  intl: ^0.18.1

添加完成后,执行以下命令安装依赖:

flutter pub get

四、完整功能代码实现(可直接运行)

新建并修改 lib/main.dart 文件,写入完整代码(全程带注释,新手可快速理解),实现账单添加、删除、收支分类、余额统计、数据持久化等核心功能:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'dart:convert';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter鸿蒙本地记账',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const AccountPage(),
      // 关闭调试标签,适配正式展示
      debugShowCheckedModeBanner: false,
    );
  }
}

// 记账主页面
class AccountPage extends StatefulWidget {
  const AccountPage({super.key});

  
  State<AccountPage> createState() => _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {
  // 金额输入控制器
  final TextEditingController _amountController = TextEditingController();
  // 备注输入控制器
  final TextEditingController _noteController = TextEditingController();
  // 账单列表(存储所有账单数据)
  List<Map<String, dynamic>> _accountList = [];
  // 本地存储实例(shared_preferences)
  late SharedPreferences _prefs;
  // 收支类型:true=收入,false=支出
  bool _isIncome = false;

  // 页面初始化:加载本地存储的账单数据
  
  void initState() {
    super.initState();
    _initPrefs();
  }

  // 初始化本地存储,加载历史账单
  Future<void> _initPrefs() async {
    _prefs = await SharedPreferences.getInstance();
    _loadAccounts();
  }

  // 加载本地账单数据(从shared_preferences读取)
  void _loadAccounts() {
    setState(() {
      // 读取存储的字符串列表,无数据则返回null
      List<String>? savedAccounts = _prefs.getStringList('accountList');
      if (savedAccounts != null) {
        // 将字符串转为Map对象,存入账单列表
        _accountList = savedAccounts.map((e) => jsonDecode(e)).toList();
      }
    });
  }

  // 添加账单(核心方法)
  Future<void> _addAccount() async {
    // 获取输入的金额和备注,去除空格
    String amountStr = _amountController.text.trim();
    String note = _noteController.text.trim();

    // 校验:金额不能为空
    if (amountStr.isEmpty) {
      Fluttertoast.showToast(msg: "请输入记账金额");
      return;
    }

    // 将金额转为double类型(保留两位小数)
    double amount = double.parse(amountStr);
    // 格式化当前时间(yyyy-MM-dd HH:mm)
    String time = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());

    // 构建账单对象
    Map<String, dynamic> account = {
      'amount': amount,       // 金额
      'isIncome': _isIncome,  // 收支类型
      'note': note,           // 备注
      'time': time            // 记账时间
    };

    // 更新页面状态,添加新账单
    setState(() {
      _accountList.add(account);
      // 清空输入框
      _amountController.clear();
      _noteController.clear();
    });

    // 保存账单到本地存储
    await _saveAccounts();
    // 鸿蒙平台提示:添加成功
    Fluttertoast.showToast(msg: "账单添加成功");
  }

  // 保存账单到本地存储(shared_preferences)
  Future<void> _saveAccounts() async {
    // 将账单列表转为字符串列表(shared_preferences不支持直接存储Map)
    List<String> accountStrList = _accountList.map((e) => jsonEncode(e)).toList();
    // 存入本地存储
    await _prefs.setStringList('accountList', accountStrList);
  }

  // 删除账单
  Future<void> _deleteAccount(int index) async {
    setState(() {
      _accountList.removeAt(index);
    });
    // 更新本地存储
    await _saveAccounts();
    // 鸿蒙提示:删除成功
    Fluttertoast.showToast(msg: "账单删除成功");
  }

  // 计算当前余额(收入-支出)
  double _calculateBalance() {
    double balance = 0.0;
    for (var account in _accountList) {
      if (account['isIncome']) {
        balance += account['amount']; // 收入加金额
      } else {
        balance -= account['amount']; // 支出减金额
      }
    }
    return balance;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Flutter+鸿蒙 本地记账"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 金额输入框
            TextField(
              controller: _amountController,
              keyboardType: TextInputType.numberWithOptions(decimal: true),
              decoration: const InputDecoration(
                hintText: "请输入金额(例:100.00)",
                border: OutlineInputBorder(),
                labelText: "金额",
              ),
            ),
            const SizedBox(height: 12),
            // 备注输入框
            TextField(
              controller: _noteController,
              decoration: const InputDecoration(
                hintText: "请输入备注(例:吃饭、工资)",
                border: OutlineInputBorder(),
                labelText: "备注",
              ),
            ),
            const SizedBox(height: 12),
            // 收支类型切换(开关)
            Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                const Text("收支类型:"),
                const SizedBox(width: 10),
                Switch(
                  value: _isIncome,
                  onChanged: (value) {
                    // 切换收支类型,更新页面
                    setState(() {
                      _isIncome = value;
                    });
                  },
                ),
                const SizedBox(width: 10),
                // 显示当前收支类型
                Text(_isIncome ? "收入" : "支出", style: const TextStyle(fontSize: 16)),
              ],
            ),
            const SizedBox(height: 16),
            // 添加账单按钮
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _addAccount,
                child: const Text("添加账单", style: TextStyle(fontSize: 16)),
              ),
            ),
            const SizedBox(height: 20),
            // 当前余额展示
            Text(
              "当前余额:¥${_calculateBalance().toStringAsFixed(2)}",
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.green,
              ),
            ),
            const Divider(height: 20),
            // 账单列表(滚动展示)
            Expanded(
              child: _accountList.isEmpty
                  ? const Center(
                      child: Text(
                        "暂无账单,快去添加吧~",
                        style: TextStyle(fontSize: 16, color: Colors.grey),
                      ),
                    )
                  : ListView.builder(
                      itemCount: _accountList.length,
                      itemBuilder: (context, index) {
                        var account = _accountList[index];
                        return ListTile(
                          // 记账时间
                          title: Text(account['time']),
                          // 备注
                          subtitle: Text(account['note']),
                          // 金额(收入红色,支出绿色)
                          trailing: Text(
                            "${account['isIncome'] ? '+' : '-'}¥${account['amount'].toStringAsFixed(2)}",
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                              color: account['isIncome'] ? Colors.green : Colors.red,
                            ),
                          ),
                          // 长按删除账单
                          onLongPress: () => _deleteAccount(index),
                        );
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }
}

五、鸿蒙6.0(API20+)专属适配

5.1 鸿蒙权限配置

由于APP需要使用本地存储保存账单数据,需在 openharmony/app/src/main/module.json5 文件中添加鸿蒙API20规范的存储权限,否则会导致数据无法保存:

{
  "module": {
    // 其他配置不变,添加以下权限配置
    "requestPermissions": [
      {
        "name": "ohos.permission.WRITE_LOCAL_STORAGE",
        "reason": "用于保存记账账单数据"
      },
      {
        "name": "ohos.permission.READ_LOCAL_STORAGE",
        "reason": "用于读取历史记账账单"
      }
    ]
  }
}

5.2 鸿蒙屏幕适配

适配鸿蒙全面屏、折叠屏,避免界面被系统导航栏遮挡,在 lib/main.dartMaterialApp 中添加以下代码:

builder: (context, child) {
  return MediaQuery(
    data: MediaQuery.of(context).copyWith(
      viewPadding: EdgeInsets.zero, // 忽略系统底部导航栏遮挡
    ),
    child: child!,
  );
},

六、项目编译与运行

6.1 运行准备

  1. 连接鸿蒙6.0真机(开启开发者模式 + USB调试),或启动鸿蒙6.0模拟器;
  2. 打开命令行,进入项目根目录;
  3. 执行以下命令查看已连接的鸿蒙设备:
flutter devices

6.2 执行运行

复制上一步查看的设备ID,执行以下命令运行项目:

flutter run -d 设备ID

等待编译完成后,APP会自动安装到鸿蒙设备/模拟器中,即可正常使用。

七、功能效果验证

确保以下核心功能正常运行,符合鸿蒙6.0设备适配要求:

  1. ✅ 输入金额、备注,切换收支类型,点击“添加账单”可成功添加;
  2. ✅ 鸿蒙设备弹出Toast提示(添加成功、删除成功);
  3. ✅ 账单列表正常展示,长按可删除账单;
  4. ✅ 余额实时计算,收入/支出金额颜色区分;
  5. ✅ 重启APP后,账单数据不丢失(本地持久化生效);
  6. ✅ 界面适配鸿蒙全面屏,无遮挡问题。

八、常见问题与解决方案

问题1:账单无法保存,提示“无权限”

解决方案:检查 module.json5 中的存储权限是否添加,重启APP并在设备上授权存储权限。

问题2:三方库在鸿蒙上不生效(如Toast不弹出)

解决方案:执行 flutter pub upgrade 更新三方库到最新稳定版,确保版本兼容鸿蒙API20。

问题3:APP启动白屏

解决方案:检查 openharmony/app/build.gradleminSdkVersion 是否设置为20,确认鸿蒙设备/模拟器版本≥6.0。

问题4:金额输入异常(无法输入小数)

解决方案:确保 TextFieldkeyboardType 设置为 TextInputType.numberWithOptions(decimal: true),支持小数输入。

总结

本文通过 Flutter集成三方库开发鸿蒙本地记账APP 的实战案例,完整覆盖了从环境配置、项目创建、三方库集成,到功能开发、鸿蒙适配、运行测试的全流程。借助 shared_preferences 实现数据持久化,fluttertoast 适配鸿蒙消息提示,intl 统一格式展示,大幅简化了开发流程,同时保证了APP在鸿蒙6.0(API20+)设备上的稳定性和兼容性。

该案例代码可直接复用,开发者可在此基础上扩展更多功能(如账单分类、月度统计等),是Flutter+鸿蒙跨端开发入门的优质实战模板。

接下来我继续为你生成第四篇:图片浏览器APP文章吗?

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐