Flutter for OpenHarmony生活助手App实战:数据统计可视化实现
本文介绍了数据可视化在记账App中的重要性及实现方法。通过Flutter框架构建统计页面,包含概览卡片和三类图表:习惯趋势折线图、财务柱状图和任务饼图。文章重点分析了折线图设计,包括隐藏网格线/边框以简化界面、曲线展示趋势变化等优化点,并强调多维度统计和图表类型选择对数据呈现的关键作用。150字

说起数据统计,我自己以前总觉得这是个可有可无的功能。但用了一段时间记账App后,我发现数据统计真的很有用。看着图表,能清楚地知道自己的习惯完成情况、收支状况、任务进度。这种可视化的反馈,比单纯的数字列表有用多了。
为什么数据可视化这么重要
在开始写代码之前,我先想清楚了这个功能的价值。
第一个是直观性。人脑对图形的处理速度比文字快得多。一张图表能传达的信息,可能需要一大段文字才能说清楚。比如看到一条上升的曲线,就知道趋势在变好;看到一个大的饼图扇区,就知道占比很高。
第二个是激励作用。看着习惯完成率从60%提升到85%,会有成就感。看着收入曲线一点点上升,会有动力继续努力。这种正向反馈对坚持很重要。
第三个是发现问题。通过数据分析,能发现一些平时注意不到的问题。比如发现某个月支出突然增加,就要反思是不是花钱太随意了。发现某个习惯完成率很低,就要想办法改进。
功能设计的思路
在设计这个功能时,我考虑了以下几个方面。
多维度的统计
不能只统计一个维度,要从多个角度展示数据。比如习惯统计、财务统计、任务统计,每个维度都有自己的图表。
多种图表类型
不同的数据适合不同的图表。趋势数据用折线图,对比数据用柱状图,占比数据用饼图。选对图表类型,能让数据更清晰。
概览卡片
在图表上方放几个概览卡片,显示关键指标。比如总习惯数、总支出、完成率。这样用户不用看图表,就能知道大概情况。
页面整体结构
先看页面的基本框架:
class StatisticsPage extends StatelessWidget {
const StatisticsPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('数据统计'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildOverviewCards(),
SizedBox(height: 24.h),
_buildHabitChart(),
SizedBox(height: 24.h),
_buildFinanceChart(),
SizedBox(height: 24.h),
_buildTaskChart(),
],
),
),
);
}
}
StatelessWidget的选择
这里用了StatelessWidget,因为页面只是展示数据,没有用户交互导致的状态变化。数据从外部传入或从数据库读取,不需要在页面内部管理状态。
页面布局的四个部分
页面分成四块:概览卡片、习惯图表、财务图表、任务图表。用SizedBox隔开,间距设置为24,看起来不会太挤。
外层用SingleChildScrollView包裹,这样内容超出屏幕时可以滚动。
概览卡片的设计
页面顶部是三个概览卡片,显示关键指标:
Widget _buildOverviewCards() {
return Row(
children: [
Expanded(
child: _buildCard('总习惯数', '12', Colors.green),
),
SizedBox(width: 12.w),
Expanded(
child: _buildCard('总支出', '¥3.2K', Colors.red),
),
SizedBox(width: 12.w),
Expanded(
child: _buildCard('完成率', '85%', Colors.blue),
),
],
);
}
Widget _buildCard(String label, String value, Color color) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 4.h),
Text(
label,
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
);
}
Row的均分布局
三个卡片用Row横向排列,每个卡片用Expanded包裹,这样它们会均分宽度。卡片之间用SizedBox隔开,间距12。
卡片的颜色设计
每个卡片有自己的主题色:
- 总习惯数用绿色,绿色代表健康、成长
- 总支出用红色,红色代表支出、警示
- 完成率用蓝色,蓝色代表理性、稳定
背景色是主题色的10%透明度,这样既有颜色区分,又不会太刺眼。
数值的视觉层级
数值用24号大字,加粗,主题色。标签用12号小字,灰色。这种层次关系让用户的视线自然地被数值吸引。
数值放在上面,标签放在下面,符合从重要到次要的阅读顺序。
习惯完成趋势图
第一个图表是习惯完成趋势,用折线图展示:
Widget _buildHabitChart() {
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: LineChart(
LineChartData(
gridData: const FlGridData(show: false),
titlesData: const FlTitlesData(
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: const [
FlSpot(0, 3),
FlSpot(1, 4),
FlSpot(2, 3.5),
FlSpot(3, 5),
FlSpot(4, 4),
FlSpot(5, 6),
FlSpot(6, 5.5),
],
isCurved: true,
color: Colors.green,
barWidth: 3,
dotData: const FlDotData(show: true),
),
],
),
),
),
],
),
);
}
为什么选择折线图
习惯完成趋势是时间序列数据,折线图最合适。折线的走向能清楚地展示趋势:上升表示进步,下降表示退步,平稳表示保持。
LineChart的关键参数
gridData: const FlGridData(show: false)隐藏网格线,让图表更简洁。网格线虽然能帮助读数,但在这个场景下不是必需的,反而会让图表显得杂乱。
titlesData设置所有轴的标签都不显示。因为这是个趋势图,重点是看走向,不是看具体数值。
borderData: FlBorderData(show: false)隐藏边框,和网格线一样,去掉不必要的元素。
折线的样式
isCurved: true让折线变成曲线,看起来更平滑。color: Colors.green用绿色,和习惯的主题色一致。barWidth: 3设置线宽为3,不会太粗也不会太细。
dotData: const FlDotData(show: true)显示数据点,让用户知道每个点的位置。
数据点的设计
数据点用FlSpot表示,第一个参数是X坐标,第二个参数是Y坐标。这里有7个点,代表一周的数据。
从数据可以看出,习惯完成数量在波动,但整体趋势是上升的。这种可视化比单纯的数字列表直观多了。
收支对比图
第二个图表是收支对比,用柱状图展示:
Widget _buildFinanceChart() {
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: const FlTitlesData(
show: false,
),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(toY: 8000, color: Colors.green),
BarChartRodData(toY: 3000, color: Colors.red),
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(toY: 8200, color: Colors.green),
BarChartRodData(toY: 3200, color: Colors.red),
]),
BarChartGroupData(x: 2, barRods: [
BarChartRodData(toY: 8500, color: Colors.green),
BarChartRodData(toY: 3500, color: Colors.red),
]),
],
),
),
),
],
),
);
}
为什么选择柱状图
收支对比是两组数据的对比,柱状图最合适。两根柱子并排,高度对比很直观。绿色柱子是收入,红色柱子是支出,颜色也有明确的含义。
BarChart的关键参数
alignment: BarChartAlignment.spaceAround让柱子均匀分布,两边留白。maxY: 10000设置Y轴最大值,这个值要根据实际数据动态调整。
barTouchData: BarTouchData(enabled: false)禁用触摸交互,因为这个图表只是展示,不需要交互。
分组柱状图的实现
每个BarChartGroupData代表一组柱子,x是位置,barRods是这组里的柱子。
每组有两根柱子,第一根是收入(绿色),第二根是支出(红色)。这样三个月的收支对比一目了然。
从数据可以看出,收入在稳步增长,支出也在增长,但增长幅度小于收入。这是个好趋势。
任务完成情况图
第三个图表是任务完成情况,用饼图展示:
Widget _buildTaskChart() {
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: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: 40.r,
sections: [
PieChartSectionData(
value: 85,
title: '85%',
color: Colors.green,
radius: 50.r,
),
PieChartSectionData(
value: 15,
title: '15%',
color: Colors.grey,
radius: 50.r,
),
],
),
),
),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildLegend('已完成', Colors.green),
SizedBox(width: 20.w),
_buildLegend('未完成', Colors.grey),
],
),
],
),
);
}
Widget _buildLegend(String label, Color color) {
return Row(
children: [
Container(
width: 12.w,
height: 12.w,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
SizedBox(width: 4.w),
Text(label, style: TextStyle(fontSize: 12.sp)),
],
);
}
为什么选择饼图
任务完成情况是占比数据,饼图最合适。扇区的大小直观地展示了占比关系。绿色扇区占85%,一眼就能看出完成率很高。
PieChart的关键参数
sectionsSpace: 2设置扇区之间的间隔为2,让扇区之间有个小缝隙,看起来更清晰。
centerSpaceRadius: 40.r设置中心空白区域的半径为40,这样饼图就变成了环形图。环形图比实心饼图更现代,也更容易看清占比。
扇区的设计
每个PieChartSectionData代表一个扇区,value是数值,title是显示的文字,color是颜色,radius是半径。
这里有两个扇区:已完成(绿色,85%)和未完成(灰色,15%)。绿色表示正向,灰色表示中性。
图例的必要性
饼图下方加了图例,说明每个颜色代表什么。虽然颜色的含义比较明显,但加上图例更规范,避免歧义。
图例用小圆点和文字组成,简洁明了。
数据的动态加载
实际项目中,数据应该从数据库读取,而不是硬编码。可以用FutureBuilder来处理异步数据加载:
FutureBuilder<StatisticsData>(
future: _loadStatisticsData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('加载失败:${snapshot.error}'));
}
if (!snapshot.hasData) {
return const Center(child: Text('暂无数据'));
}
final data = snapshot.data!;
return _buildCharts(data);
},
)
三种状态的处理
- 加载中:显示loading指示器
- 加载失败:显示错误信息
- 加载成功:显示图表
这种处理方式很规范,用户体验也好。
图表的交互增强
虽然目前图表只是展示,但可以加一些交互,让用户能看到更多信息。
折线图的触摸交互
用户触摸折线图时,显示该点的具体数值:
LineTouchData(
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Colors.blueGrey.withOpacity(0.8),
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'${spot.y.toInt()} 个',
const TextStyle(color: Colors.white),
);
}).toList();
},
),
)
柱状图的触摸交互
用户触摸柱状图时,显示该柱子的具体数值:
BarTouchData(
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey.withOpacity(0.8),
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
'¥${rod.toY.toInt()}',
const TextStyle(color: Colors.white),
);
},
),
)
饼图的触摸交互
用户触摸饼图时,该扇区会稍微突出:
PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {
setState(() {
if (event is FlTapUpEvent && pieTouchResponse != null) {
final touchedIndex = pieTouchResponse.touchedSection?.touchedSectionIndex;
_touchedIndex = touchedIndex;
}
});
},
)
实际使用体验
这个数据统计页面我自己用了一段时间,感觉很有用。每周看一次,就能知道自己的进步情况。
概览卡片很直观,一眼就能看到关键指标。习惯完成趋势图让我知道自己是在进步还是退步。收支对比图让我知道财务状况是否健康。任务完成情况图让我知道自己的执行力如何。
这些图表给了我很多正向反馈。看到习惯完成率从60%提升到85%,会很有成就感。看到收入曲线上升,会更有动力工作。
可以改进的地方
如果要做得更完善,可以考虑以下几点。
时间范围的筛选
用户可能想看不同时间段的数据,比如本周、本月、本年。可以加个时间选择器,让用户自己选择。
更多维度的统计
除了习惯、财务、任务,还可以统计健康数据、学习数据等。每个维度都有自己的图表。
数据的导出
用户可能想把数据导出成Excel或PDF,方便分析或分享。可以加个导出按钮。
对比分析
可以加个同比环比的功能,比如"本月收入比上月增长10%",“本周习惯完成率比上周提高5%”。这种对比能让用户更清楚地看到变化。
目标设定
用户可以设定目标,比如"本月收入目标10000元",“习惯完成率目标90%”。图表上显示目标线,让用户知道距离目标还有多远。
小结
今天实现了数据统计可视化功能,用到了折线图、柱状图、饼图等图表。核心是用fl_chart包绘制图表,让数据以可视化的方式呈现。
这个功能虽然不是最核心的,但对用户价值很大。数据可视化能让用户更直观地了解自己的情况,发现问题,获得激励。
在实现过程中,我特别注重图表类型的选择。趋势数据用折线图,对比数据用柱状图,占比数据用饼图。选对图表类型,能让数据更清晰。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)