Flutter for OpenHarmony生活助手App实战:收入分析统计实现
本文介绍了一个收入分析页面的Flutter实现,重点阐述了收入分析的价值和UI设计要点。作者指出收入分析不仅能提供财务规划依据,还能通过正向激励提升记账动力。页面采用三部分布局:顶部总收入卡片使用绿色渐变突出本月收入,中间柱状图展示月度趋势,底部为收入明细列表。技术实现上选用StatelessWidget构建,通过fl_chart库创建交互式图表,并详细讲解了视觉层级、色彩选择和激励性文案的设计思

说起记账,大多数人都只关注支出,觉得知道钱花哪儿了就够了。但我自己用了一段时间记账App后发现,收入分析同样重要。看着收入曲线一点点上升,那种成就感比看支出账单舒服多了。而且通过分析收入来源,能更清楚地知道哪些渠道能带来收益,对个人财务规划很有帮助。
为什么要做收入分析
在设计这个功能之前,我先想明白了几个问题。
第一个是动力问题。记账这件事,如果只看支出,会越记越沮丧。每天看着钱往外流,谁都不开心。但如果能看到收入的增长趋势,就会有正向激励。我自己就是这样,每次看到本月收入比上月多了几百块,就觉得努力没白费。
第二个是规划问题。知道自己每个月大概能收入多少,才能合理安排支出。比如这个月收入8000,那支出最好控制在6000以内,剩下2000可以存起来或者投资。如果不清楚收入情况,很容易花超。
第三个是来源分析。现在很多人不只有工资这一项收入,可能还有兼职、投资、奖金等。通过分析各个来源的占比,能知道哪些渠道值得继续投入。比如发现兼职收入占比越来越高,说明这条路走对了。
页面整体结构
先看页面的基本框架:
class IncomeAnalysisPage extends StatelessWidget {
const IncomeAnalysisPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('收入分析'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildTotalIncome(),
SizedBox(height: 24.h),
_buildMonthlyChart(),
SizedBox(height: 24.h),
_buildIncomeList(),
],
),
),
);
}
}
为什么用StatelessWidget
你可能会问,这里为什么不用StatefulWidget?因为目前页面只是展示数据,没有用户交互导致的状态变化。如果后续要加筛选、排序等功能,再改成StatefulWidget也不迟。
页面布局的三个部分
整个页面分成三块:顶部的总收入卡片、中间的趋势图表、底部的收入明细列表。这个顺序是经过考虑的,用户最关心的是总数,其次是趋势,最后才是明细。
用SingleChildScrollView包裹,是因为内容可能超出一屏。Column里的三个组件用SizedBox隔开,间距设置为24,看起来不会太挤。
顶部总收入卡片的设计
这个卡片是整个页面的视觉焦点,我花了不少心思:
Widget _buildTotalIncome() {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.green, Colors.lightGreen],
),
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'本月总收入',
style: TextStyle(color: Colors.white70, fontSize: 14.sp),
),
SizedBox(height: 8.h),
Text(
'¥ 8,500.00',
style: TextStyle(
color: Colors.white,
fontSize: 36.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.h),
Text(
'较上月增长 5%',
style: TextStyle(color: Colors.white70, fontSize: 14.sp),
),
],
),
);
}
绿色渐变的选择
收入用绿色,这是约定俗成的。绿色代表增长、正向、健康。我用了从Colors.green到Colors.lightGreen的渐变,比纯色更有层次感。这个渐变是从上到下的,给人一种向上生长的感觉。
金额的视觉层级
注意看三行文字的样式:
- 第一行"本月总收入"用了
white70,半透明的白色,作为标签 - 第二行金额用了
36.sp的大字号,fontWeight.bold加粗,纯白色,这是视觉焦点 - 第三行"较上月增长5%"又回到
white70,作为辅助信息
这种层级关系很重要。用户的视线会自然地被大号加粗的金额吸引,然后再看上下的说明文字。
增长率的激励作用
"较上月增长5%"这行字别小看,它能给用户正向反馈。看到收入在增长,会有成就感。如果是负增长,可以改成红色,提醒用户注意。
实际项目中,这个增长率应该是动态计算的:
final lastMonthIncome = 8095.0;
final currentMonthIncome = 8500.0;
final growthRate = ((currentMonthIncome - lastMonthIncome) / lastMonthIncome * 100).toStringAsFixed(1);
收入趋势图表的实现
图表是这个页面的核心,我用了fl_chart包的柱状图:
Widget _buildMonthlyChart() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'收入趋势',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 20.h),
SizedBox(
height: 200.h,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: 10000,
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
const months = ['1月', '2月', '3月', '4月', '5月', '6月'];
if (value.toInt() >= 0 && value.toInt() < months.length) {
return Text(months[value.toInt()], style: TextStyle(fontSize: 12.sp));
}
return const Text('');
},
),
),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [BarChartRodData(toY: 8000, color: Colors.green)]),
BarChartGroupData(x: 1, barRods: [BarChartRodData(toY: 8200, color: Colors.green)]),
BarChartGroupData(x: 2, barRods: [BarChartRodData(toY: 8100, color: Colors.green)]),
BarChartGroupData(x: 3, barRods: [BarChartRodData(toY: 8300, color: Colors.green)]),
BarChartGroupData(x: 4, barRods: [BarChartRodData(toY: 8400, color: Colors.green)]),
BarChartGroupData(x: 5, barRods: [BarChartRodData(toY: 8500, color: Colors.green)]),
],
),
),
),
],
),
);
}
为什么选择柱状图
收入趋势用柱状图比折线图更合适。柱状图能清楚地展示每个月的具体数值,而且柱子的高度对比很直观。看到柱子一个比一个高,就知道收入在增长。
BarChart的关键参数
alignment: BarChartAlignment.spaceAround让柱子均匀分布,两边留白。maxY: 10000设置Y轴最大值,这个值要根据实际数据动态调整,太小会导致柱子顶到天花板,太大会让柱子看起来很矮。
底部标签的处理
getTitlesWidget这个回调很关键,它决定了X轴显示什么文字:
getTitlesWidget: (value, meta) {
const months = ['1月', '2月', '3月', '4月', '5月', '6月'];
if (value.toInt() >= 0 && value.toInt() < months.length) {
return Text(months[value.toInt()], style: TextStyle(fontSize: 12.sp));
}
return const Text('');
}
value是柱子的索引,从0开始。我定义了一个月份数组,根据索引取对应的月份名称。这里要做边界检查,避免数组越界。
隐藏其他轴的标签
左、上、右三个轴的标签都设置为不显示,因为不需要。只保留底部的月份标签就够了。borderData: FlBorderData(show: false)隐藏边框,让图表看起来更简洁。
柱子数据的构建
每个柱子用BarChartGroupData表示,x是位置,barRods是柱子的数据。toY是柱子的高度,对应收入金额。所有柱子都用绿色,和顶部卡片的主题色保持一致。
从数据可以看出,收入从8000逐渐增长到8500,这是一个稳定上升的趋势。用户看到这个图表,会很有成就感。
收入明细列表的设计
图表下方是具体的收入明细,让用户知道钱是从哪儿来的:
Widget _buildIncomeList() {
final incomes = [
{'source': '工资', 'amount': 8000.0, 'date': '2024-01-05'},
{'source': '奖金', 'amount': 500.0, 'date': '2024-01-15'},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'收入明细',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 12.h),
...incomes.map((income) => Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
income['source'] as String,
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 4.h),
Text(
income['date'] as String,
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
Text(
'+¥${income['amount']}',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
)),
],
);
}
数据结构的设计
每条收入记录包含三个字段:来源、金额、日期。这是最基本的信息。实际项目中,还应该加上分类、备注等字段。
展开运算符的妙用
注意看...incomes.map()这行代码,三个点是Dart的展开运算符。map返回的是一个Iterable,展开运算符把它拆成一个个Widget,直接放进Column的children里。
这样写比用ListView.builder简洁,因为数据量不大,不需要懒加载。
卡片布局的细节
每条记录用一个白色卡片展示,左边是来源和日期,右边是金额。mainAxisAlignment: MainAxisAlignment.spaceBetween让左右两边分别靠边,中间自动留白。
金额的正向标识
金额前面加了个加号+¥,表示这是收入。颜色用绿色,和支出的红色形成对比。这种视觉区分很重要,用户一眼就能看出是收入还是支出。
实际使用中的改进
这个页面目前是静态数据,实际使用时需要做一些改进。
数据来源的处理
收入数据应该从数据库读取,而不是硬编码。可以用FutureBuilder来处理异步数据加载:
FutureBuilder<List<Income>>(
future: _loadIncomeData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return _buildIncomeList(snapshot.data!);
}
return const CircularProgressIndicator();
},
)
时间范围的筛选
用户可能想看不同时间段的收入,比如本周、本月、本年。可以在AppBar加个下拉菜单,让用户选择时间范围。
收入来源的分类统计
可以加个饼图,展示各个来源的占比。比如工资占80%,奖金占10%,兼职占10%。这样能更清楚地看出收入结构。
同比环比的对比
除了"较上月增长5%“,还可以加上同比数据,比如"较去年同期增长15%”。这样能看出长期趋势。
图表库的选择
Flutter有很多图表库,我选择fl_chart是因为它功能强大、文档完善、社区活跃。
fl_chart的优点
- 支持多种图表类型:折线图、柱状图、饼图、雷达图等
- 高度可定制:颜色、样式、动画都能自定义
- 性能不错:即使数据量大也能流畅渲染
- 文档详细:官方示例很全面,遇到问题容易找到解决方案
其他可选的图表库
如果fl_chart不满足需求,还可以试试:
charts_flutter:Google官方的图表库,但已经不维护了syncfusion_flutter_charts:功能最强大,但是商业使用要付费graphic:基于Grammar of Graphics理论,适合复杂的数据可视化
性能优化的考虑
虽然这个页面数据量不大,但还是要考虑性能。
图表的渲染优化
fl_chart的图表是用Canvas绘制的,性能已经很好了。但如果数据点特别多,可以考虑降采样,只显示关键的数据点。
列表的懒加载
如果收入明细很多,应该用ListView.builder代替Column+map的方式。ListView.builder只会构建可见的item,内存占用更少。
数据缓存
从数据库读取的数据可以缓存起来,避免每次进入页面都重新查询。可以用Provider或GetX来管理状态。
用户体验的细节
除了功能实现,用户体验也很重要。
加载状态的提示
数据加载时,应该显示一个loading指示器,让用户知道正在加载。不要让页面空白,用户会以为卡住了。
空状态的处理
如果用户还没有收入记录,应该显示一个友好的空状态页面,引导用户添加第一条记录。可以放个插图,配上"还没有收入记录,快去添加吧"这样的文字。
错误处理
如果数据加载失败,要给用户明确的提示,并提供重试按钮。不要只是打印错误日志,用户看不到。
动画效果
图表可以加个入场动画,柱子从下往上长出来,会更有趣。fl_chart支持动画,只需要设置animate: true。
数据统计的扩展
收入分析还可以做得更深入。
收入预测
根据历史数据,预测下个月的收入。可以用简单的线性回归,或者更复杂的时间序列模型。
收入目标设定
用户可以设定月收入目标,比如10000元。页面上显示当前进度,激励用户努力达成目标。
收入来源的趋势分析
不仅看总收入的趋势,还要看各个来源的趋势。比如工资稳定,但兼职收入在增长,说明副业做得不错。
收支对比
把收入和支出放在一起对比,看看每个月能存下多少钱。如果支出大于收入,要及时调整。
小结
今天实现了收入分析统计功能,用到了渐变卡片、柱状图、列表展示等组件。核心是用fl_chart绘制收入趋势图,让用户直观地看到收入变化。
这个功能虽然看起来简单,但对用户的价值很大。看着收入曲线上升,会有成就感和动力。通过分析收入来源,能更好地规划财务。
在实现过程中,我特别注重视觉设计。绿色渐变的卡片、清晰的图表、简洁的列表,这些细节都是为了让用户用得舒服。毕竟,一个好看又好用的工具,才能让用户坚持记账。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)