Flutter for OpenHarmony 个人理财管理App实战 - 首页总览
本文介绍了理财应用首页的设计思路与实现方案。首页采用卡片式布局,从上至下依次展示资产总览、月份选择器、月度收支统计、快捷功能入口和最近交易记录。设计重点在于突出关键财务数据(总资产、净资产、本月结余),使用绿色表示收入、红色表示支出的通用配色方案。页面实现基于Flutter框架,利用GetX进行状态管理,通过Obx实现数据响应式更新。资产总览卡片采用主题色背景和大字号显示,确保用户能快速获取核心财
首页是用户打开应用后看到的第一个界面,需要在有限的空间内展示最重要的信息。对于理财应用来说,用户最关心的是:我有多少钱、这个月花了多少、最近的收支记录。这篇文章详细介绍首页的设计思路和实现细节。
首页布局规划
首页采用卡片式布局,从上到下依次是:
- 资产总览卡片 - 展示总资产、净资产、本月结余
- 月份选择器 - 切换查看不同月份的数据
- 月度收支统计 - 本月收入和支出的汇总
- 快捷功能入口 - 账户、分类、报表、目标的快速入口
- 最近交易记录 - 最新的几笔收支记录
这种布局信息密度适中,用户一眼就能看到关键数据,同时也能快速进入其他功能。卡片式设计让每个信息区块有清晰的边界,用户阅读时不会感到混乱。
页面基础结构
先看 HomePage 的整体框架,包括导入依赖和颜色常量定义:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../core/services/category_service.dart';
import '../../data/models/transaction_model.dart';
import '../../routes/app_pages.dart';
import 'home_controller.dart';
const _primaryColor = Color(0xFF2E7D32);
const _incomeColor = Color(0xFF4CAF50);
const _expenseColor = Color(0xFFE53935);
const _textSecondary = Color(0xFF757575);
导入部分包含了 Flutter 核心库、屏幕适配库、GetX 状态管理、日期格式化库,以及项目内部的服务和模型。这些依赖各司其职,flutter_screenutil 负责屏幕适配,intl 负责日期格式化,GetX 负责状态管理和路由导航。
颜色常量定义在文件顶部,_incomeColor 用绿色表示收入,_expenseColor 用红色表示支出,这是财务软件的通用配色方案。用户看到绿色就知道是收入,看到红色就知道是支出,不需要额外的学习成本。_textSecondary 用于次要文字,灰色调不会抢主要内容的风头。
页面类定义和 build 方法的整体结构:
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
final controller = Get.put(HomeController());
return Scaffold(
appBar: AppBar(
title: const Text('个人理财管家'),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => Get.toNamed(Routes.search)
),
IconButton(
icon: const Icon(Icons.calendar_month),
onPressed: () => Get.toNamed(Routes.calendar)
),
],
),
HomePage 继承自 StatelessWidget,因为所有状态都由 HomeController 管理,页面本身不需要持有状态。Get.put 在 build 方法中调用,GetX 会自动处理重复注册的情况,如果 Controller 已存在就返回现有实例。
AppBar 右侧放了两个快捷入口:搜索和日历。搜索图标让用户能快速查找历史记录,日历图标提供按日期浏览的视图。这两个功能使用频率较高,放在首页顶部可以减少用户的操作路径,提升使用效率。actions 数组可以放多个按钮,Flutter 会自动处理它们的排列。
页面主体使用 RefreshIndicator 实现下拉刷新:
body: RefreshIndicator(
onRefresh: () async => controller.refresh(),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildOverviewCard(controller),
SizedBox(height: 16.h),
_buildMonthSelector(controller),
SizedBox(height: 16.h),
_buildMonthlyStats(controller),
SizedBox(height: 16.h),
_buildQuickActions(),
SizedBox(height: 16.h),
_buildRecentTransactions(controller),
],
),
),
),
);
}
RefreshIndicator 实现下拉刷新功能,这是移动端应用的标准交互模式。用户下拉页面时会触发 onRefresh 回调,调用 controller.refresh() 重新加载数据。SingleChildScrollView 让整个页面可以滚动,AlwaysScrollableScrollPhysics 确保即使内容不足一屏也能触发下拉刷新。
padding 使用 16.w 作为统一的边距,.w 是 flutter_screenutil 的扩展方法,会根据屏幕宽度自动缩放。Column 的 children 按顺序排列各个组件,SizedBox 用于组件之间的间距,16.h 是一个舒适的间距值,既不会太紧凑也不会太松散。
资产总览卡片
资产总览是首页最重要的信息区域,用主题色背景突出显示:
Widget _buildOverviewCard(HomeController controller) {
return Card(
color: _primaryColor,
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('总资产',
style: TextStyle(color: Colors.white70, fontSize: 14.sp)),
SizedBox(height: 8.h),
Obx(() => Text(
'${controller.currency}${controller.totalAssets.value.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.bold
)
)),
Card 组件的 color 属性设为主题色,和白色文字形成强对比,视觉上非常突出。这种设计让用户一眼就能看到最重要的数字。Padding 设为 20.w,比普通卡片稍大一些,让内容有更多呼吸空间。
"总资产"标签用 white70 半透明白色,不会太抢眼但又清晰可见。总资产金额用 28.sp 的大字号显示,这是页面上最大的文字,强调其重要性。toStringAsFixed(2) 保留两位小数,金额显示更专业。Obx 包裹 Text 组件,当 totalAssets 变化时只会重建这一小块 UI。
资产总览卡片的下半部分显示净资产和本月结余:
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildAssetItem('净资产', controller),
_buildBalanceItem('本月结余', controller),
],
),
],
),
),
);
}
Row 组件让两个指标水平排列,MainAxisAlignment.spaceBetween 让它们分布在两端。净资产 = 总资产 - 负债,对于有信用卡的用户来说这个数字更有参考价值,因为信用卡欠款会影响实际可用资金。本月结余 = 本月收入 - 本月支出,反映当月的财务状况,让用户知道这个月是存钱了还是超支了。
这种布局在视觉上形成了一个倒三角结构:顶部是最重要的总资产,下方是两个次要指标。用户的视线自然从上往下移动,信息层次非常清晰。
净资产指标的实现:
Widget _buildAssetItem(String label, HomeController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: TextStyle(color: Colors.white70, fontSize: 12.sp)),
SizedBox(height: 4.h),
Obx(() => Text(
'${controller.currency}${controller.netWorth.value.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500
)
)),
],
);
}
_buildAssetItem 方法构建单个指标项,采用上下结构:标签在上,数值在下。标签用 12.sp 的小字号和半透明白色,数值用 16.sp 的中等字号和纯白色。这种大小和颜色的对比让数值更突出,标签起到说明作用但不会喧宾夺主。
Column 的 crossAxisAlignment 设为 start,让内容左对齐。fontWeight 设为 w500 是中等粗细,比普通文字稍粗但不像 bold 那么重,在视觉上形成适度的强调。每个数据项都用 Obx 包裹,这样当数据变化时只会重建这一小块,而不是整个卡片。
本月结余指标的实现与净资产类似:
Widget _buildBalanceItem(String label, HomeController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(label,
style: TextStyle(color: Colors.white70, fontSize: 12.sp)),
SizedBox(height: 4.h),
Obx(() => Text(
'${controller.currency}${controller.monthlyBalance.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500
)
)),
],
);
}
_buildBalanceItem 和 _buildAssetItem 的主要区别是 crossAxisAlignment 设为 end,让内容右对齐。这样两个指标一左一右,形成对称的布局。右对齐的设计也符合阅读习惯,因为本月结余放在右边,用户的视线从左到右扫过时,最后停留在这个数字上。
monthlyBalance 是一个计算属性,等于 monthlyIncome - monthlyExpense。这个值可能是正数也可能是负数,正数表示本月有结余,负数表示本月超支。在实际应用中,可以根据正负值显示不同的颜色来提醒用户。
月份选择器
用户可能想查看历史月份的数据,月份选择器提供这个能力:
Widget _buildMonthSelector(HomeController controller) {
return Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: controller.previousMonth
),
Text(
DateFormat('yyyy年MM月').format(controller.selectedMonth.value),
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)
),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: controller.nextMonth
),
],
));
}
月份选择器采用经典的左右箭头加中间文字的布局,这种设计用户非常熟悉,几乎不需要学习就能使用。左箭头切换到上个月,右箭头切换到下个月,中间显示当前选中的年月。
DateFormat 来自 intl 包,‘yyyy年MM月’ 格式化为 “2024年01月” 这样的中文格式。Row 的 mainAxisAlignment 设为 center 让整个选择器居中显示。整个组件用 Obx 包裹,当 selectedMonth 变化时会自动更新显示。
这个组件没有用 Card 包裹,视觉上更轻量,不会抢资产卡片的风头。IconButton 自带点击反馈效果,用户点击时会有水波纹动画。fontWeight 设为 w600 让日期文字稍微粗一些,更容易阅读。
月度收支统计
收入和支出用两个并排的卡片展示,让用户一目了然:
Widget _buildMonthlyStats(HomeController controller) {
return Row(
children: [
Expanded(child: _buildStatCard(
'收入',
controller.monthlyIncome,
_incomeColor,
controller.currency
)),
SizedBox(width: 12.w),
Expanded(child: _buildStatCard(
'支出',
controller.monthlyExpense,
_expenseColor,
controller.currency
)),
],
);
}
Row 组件让两个卡片水平排列,Expanded 让它们平分可用宽度。中间用 SizedBox 留出 12.w 的间距,这个间距比组件之间的 16.h 稍小一些,让两个卡片看起来更像是一组相关的信息。
收入卡片传入绿色 _incomeColor,支出卡片传入红色 _expenseColor,颜色的区分让用户不需要看文字就能分辨。这种设计遵循了"颜色即信息"的原则,绿色代表正向的收入,红色代表需要关注的支出。currency 参数传入货币符号,支持多币种显示。
单个统计卡片的实现:
Widget _buildStatCard(String label, RxDouble value, Color color, String currency) {
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle
)
),
SizedBox(width: 8.w),
Text(label,
style: TextStyle(color: _textSecondary, fontSize: 14.sp)),
]),
每个卡片左上角有一个小圆点,颜色和金额颜色一致,形成视觉关联。Container 设置 width 和 height 相等,配合 BoxShape.circle 形成正圆形。这个小圆点虽然只有 8.w 大小,但起到了重要的视觉引导作用。
标签文字用灰色 _textSecondary,字号 14.sp 比金额小一些。Row 组件让圆点和标签水平排列,SizedBox 在它们之间留出 8.w 的间距。这种布局让标签行看起来整洁有序,圆点作为视觉锚点帮助用户快速定位。
金额显示部分:
SizedBox(height: 8.h),
Obx(() => Text(
'$currency${value.value.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: color
)
)),
],
),
),
);
}
金额用 20.sp 的大字号和粗体显示,颜色和顶部的小圆点一致。这种设计让收入和支出在视觉上有明确的区分,用户扫一眼就能知道哪个是收入哪个是支出。toStringAsFixed(2) 保留两位小数,让金额显示更规范。
Obx 包裹金额 Text,当 value 变化时只重建这个文字组件。RxDouble 是 GetX 的响应式双精度浮点数类型,value.value 获取实际的 double 值。这种响应式设计让数据更新时 UI 能自动同步,不需要手动调用 setState。
快捷功能入口
把常用功能的入口放在首页,减少用户的操作路径:
Widget _buildQuickActions() {
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('快捷功能',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildQuickAction(Icons.account_balance, '账户',
() => Get.toNamed(Routes.accountList)),
_buildQuickAction(Icons.category, '分类',
() => Get.toNamed(Routes.categoryList)),
_buildQuickAction(Icons.assessment, '报表',
() => Get.toNamed(Routes.monthlyReport)),
_buildQuickAction(Icons.flag, '目标',
() => Get.toNamed(Routes.goals)),
],
),
],
),
),
);
}
快捷功能区域用 Card 包裹,和其他内容保持一致的视觉风格。标题"快捷功能"用 16.sp 字号和 w600 粗细,和其他卡片的标题样式统一。Row 的 mainAxisAlignment 设为 spaceAround,让四个入口均匀分布。
四个入口分别是:账户管理、分类管理、月度报表、理财目标。这些功能的选择是经过考虑的:账户和分类是数据管理的基础,报表是用户查看分析的入口,目标是理财规划的核心。每个入口都通过 Get.toNamed 进行路由导航,使用命名路由让代码更清晰。
单个快捷入口的实现:
Widget _buildQuickAction(IconData icon, String label, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Column(
children: [
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: _primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r)
),
child: Icon(icon, color: _primaryColor, size: 24.sp),
),
SizedBox(height: 8.h),
Text(label, style: TextStyle(fontSize: 12.sp)),
],
),
);
}
每个快捷入口采用上下结构:图标在上,文字在下。图标放在一个圆角矩形容器中,背景色是主题色的 10% 透明度,既有颜色又不会太抢眼。borderRadius 设为 12.r,.r 是响应式圆角值,会根据屏幕尺寸自动调整。
GestureDetector 处理点击事件,比 InkWell 更轻量,这里不需要水波纹效果所以选择 GestureDetector。VoidCallback 是无参数无返回值的函数类型,用于传递点击回调。图标大小 24.sp 和文字大小 12.sp 形成 2:1 的比例,视觉上比较协调。
最近交易记录
展示最新的几笔交易,让用户快速了解近期收支情况:
Widget _buildRecentTransactions(HomeController controller) {
final categoryService = Get.find<CategoryService>();
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('最近记录',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600)),
TextButton(
onPressed: () => Get.toNamed(Routes.transactionList),
child: const Text('查看全部')
),
],
),
最近交易记录区域的标题栏包含标题和"查看全部"按钮。Row 的 mainAxisAlignment 设为 spaceBetween 让它们分布在两端。TextButton 是 Material Design 的文字按钮,自带点击效果,适合这种次要操作。
Get.find() 获取分类服务的实例,用于根据 categoryId 查找分类信息。这里没有在 build 方法开头获取,而是在需要的地方获取,是因为这个服务只在这个方法中使用。这种做法让依赖关系更清晰,也方便后续重构。
交易列表的构建逻辑:
Obx(() {
final transactions = controller.recentTransactions;
if (transactions.isEmpty) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 32.h),
child: Center(child: Text(
'暂无记录',
style: TextStyle(color: _textSecondary, fontSize: 14.sp)
)),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: transactions.length > 5 ? 5 : transactions.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (_, index) {
final t = transactions[index];
final category = categoryService.getCategoryById(t.categoryId);
Obx 包裹整个列表区域,当 recentTransactions 变化时自动更新。空状态处理很重要,当没有记录时显示"暂无记录"的提示,比空白页面更友好。Padding 的 vertical 设为 32.h,让空状态提示有足够的视觉空间。
ListView.separated 比普通 ListView 多了分隔线功能,separatorBuilder 返回 Divider 组件作为分隔线。shrinkWrap: true 让 ListView 根据内容自适应高度,NeverScrollableScrollPhysics 禁用列表自身的滚动,由外层的 SingleChildScrollView 统一处理滚动。
列表最多显示 5 条记录,避免首页过长。itemCount 使用三元表达式判断,如果记录超过 5 条就只显示 5 条。categoryService.getCategoryById 根据交易的 categoryId 查找对应的分类信息,用于显示分类图标和名称。
单条交易记录的展示:
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: (category?.color ?? Colors.grey).withOpacity(0.2),
child: Icon(
category?.icon ?? Icons.help,
color: category?.color ?? Colors.grey,
size: 20.sp
),
),
title: Text(category?.name ?? '未知分类'),
subtitle: Text(DateFormat('MM-dd HH:mm').format(t.date)),
trailing: Text(
'${t.type == TransactionType.income ? '+' : '-'}${controller.currency}${t.amount.toStringAsFixed(2)}',
style: TextStyle(
color: t.type == TransactionType.income
? _incomeColor
: _expenseColor,
fontWeight: FontWeight.w600,
fontSize: 14.sp,
),
),
ListTile 是 Material Design 的列表项组件,自带 leading、title、subtitle、trailing 四个位置。contentPadding 设为 zero 去掉默认内边距,因为外层 Card 已经有 padding 了。CircleAvatar 显示分类图标,背景色是分类颜色的 20% 透明度。
category?.color 使用空安全操作符,如果 category 为 null 就使用 Colors.grey 作为默认值。subtitle 显示交易时间,格式化为 “01-15 14:30” 这样的简短格式。trailing 显示金额,收入前面加 + 号,支出前面加 - 号,颜色也根据类型变化。
点击交易记录跳转到详情页:
onTap: () => Get.toNamed(Routes.transactionDetail, arguments: t),
);
},
);
}),
],
),
),
);
}
onTap 回调使用 Get.toNamed 进行路由导航,arguments 参数传递交易对象 t。详情页可以通过 Get.arguments 获取这个对象,显示完整的交易信息并提供编辑功能。
这种参数传递方式比通过 URL 参数更灵活,可以传递任意类型的对象。但要注意,如果用户直接通过 URL 访问详情页(比如深度链接),arguments 会是 null,需要做好空值处理。
HomeController 实现
Controller 负责数据的加载和状态管理,是 MVVM 架构中的 ViewModel 层:
import 'package:get/get.dart';
import '../../core/services/transaction_service.dart';
import '../../core/services/account_service.dart';
import '../../core/services/storage_service.dart';
import '../../data/models/transaction_model.dart';
class HomeController extends GetxController {
final _transactionService = Get.find<TransactionService>();
final _accountService = Get.find<AccountService>();
final _storage = Get.find<StorageService>();
final selectedMonth = DateTime.now().obs;
final totalAssets = 0.0.obs;
final netWorth = 0.0.obs;
final monthlyIncome = 0.0.obs;
final monthlyExpense = 0.0.obs;
final recentTransactions = <TransactionModel>[].obs;
HomeController 继承自 GetxController,这是 GetX 提供的控制器基类。通过 Get.find 获取三个服务的实例:TransactionService 管理交易数据,AccountService 管理账户数据,StorageService 管理存储和设置。
状态变量都用 .obs 转为响应式类型:selectedMonth 是当前选中的月份,totalAssets 是总资产,netWorth 是净资产,monthlyIncome 和 monthlyExpense 是月度收支,recentTransactions 是最近的交易记录列表。这些响应式变量在值变化时会自动通知 UI 更新。
计算属性和生命周期方法:
String get currency => _storage.currency;
double get monthlyBalance => monthlyIncome.value - monthlyExpense.value;
void onInit() {
super.onInit();
_loadData();
ever(selectedMonth, (_) => _loadMonthlyData());
}
currency 是一个 getter,直接从 StorageService 获取货币符号。monthlyBalance 是计算属性,返回月度结余。使用 getter 而不是响应式变量,是因为这个值可以从其他值计算得出,不需要单独存储。
onInit 是 GetX 的生命周期方法,在 Controller 创建后立即调用。这里调用 _loadData 加载初始数据,然后用 ever 监听 selectedMonth 的变化。ever 是 GetX 提供的监听器,当 selectedMonth 变化时会自动调用 _loadMonthlyData 重新加载月度数据。
数据加载方法:
void _loadData() {
totalAssets.value = _accountService.totalAssets;
netWorth.value = _accountService.netWorth;
_loadMonthlyData();
_loadRecentTransactions();
}
void _loadMonthlyData() {
final start = DateTime(
selectedMonth.value.year,
selectedMonth.value.month,
1
);
final end = DateTime(
selectedMonth.value.year,
selectedMonth.value.month + 1,
0
);
monthlyIncome.value = _transactionService.getTotalIncome(
start: start,
end: end
);
monthlyExpense.value = _transactionService.getTotalExpense(
start: start,
end: end
);
}
_loadData 方法加载所有数据,包括总资产、净资产、月度数据和最近交易。totalAssets 和 netWorth 从 AccountService 获取,这两个值是所有账户余额的汇总。
_loadMonthlyData 计算选中月份的第一天和最后一天,然后调用 TransactionService 获取该时间范围内的收入和支出总额。DateTime 的构造函数很灵活,month + 1 然后 day 设为 0 就能得到当月最后一天,这是一个常用的日期计算技巧。
加载最近交易记录:
void _loadRecentTransactions() {
final list = _transactionService.allTransactions.toList();
list.sort((a, b) => b.date.compareTo(a.date));
recentTransactions.value = list.take(10).toList();
}
void previousMonth() {
selectedMonth.value = DateTime(
selectedMonth.value.year,
selectedMonth.value.month - 1
);
}
void nextMonth() {
selectedMonth.value = DateTime(
selectedMonth.value.year,
selectedMonth.value.month + 1
);
}
void refresh() {
_loadData();
}
}
_loadRecentTransactions 获取所有交易记录,按日期倒序排列,取前 10 条。toList() 创建副本再排序,避免修改原始数据。take(10) 取前 10 条,再 toList() 转为 List 赋值给响应式变量。
previousMonth 和 nextMonth 方法修改 selectedMonth,触发 ever 监听器重新加载数据。DateTime 构造函数会自动处理月份溢出,比如 month 设为 0 会变成上一年的 12 月,设为 13 会变成下一年的 1 月。
refresh 方法供下拉刷新调用,重新加载所有数据。这种设计让数据刷新逻辑集中在 Controller 中,View 层只需要调用这个方法,不需要关心具体的刷新逻辑。
性能优化考虑
首页是高频访问的页面,性能优化很重要。几个关键点:
- 用 const 构造函数创建不变的 Widget,减少重建开销
- Obx 包裹最小范围的 Widget,避免不必要的重建
- ListView 用 shrinkWrap 而不是固定高度,适应不同数据量
- 图片和图标用合适的尺寸,不要加载过大的资源
如果数据量很大,可以考虑分页加载最近交易记录,或者用 ListView.builder 的懒加载特性。但对于个人理财应用,数据量通常不会太大,这些优化可以后续按需添加。
const 的使用是 Flutter 性能优化的基础。const Widget 在编译时就确定了,运行时不需要重新创建。在 build 方法中,尽可能给 Widget 加上 const 修饰符,可以显著减少内存分配和垃圾回收的压力。
小结
首页的设计遵循了几个原则:
- 信息层次分明,最重要的数据最突出
- 操作路径短,常用功能一步可达
- 视觉风格统一,颜色有明确的含义
- 响应式更新,数据变化自动反映到 UI
资产总览卡片用主题色背景突出显示,让用户一眼就能看到最重要的数字。月度收支用绿色和红色区分,符合用户的认知习惯。快捷功能入口减少了操作路径,最近交易记录让用户快速了解近期收支。
下一篇会介绍添加交易记录页面的实现,包括金额输入、分类选择、账户选择等交互细节。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)