Flutter for OpenHarmony移动数据使用监管助手App实战 - 日详情实现
本文介绍了一个流量监控应用的日详情页面设计,主要用于展示用户某天的流量使用情况。页面包含流量汇总卡片和24小时流量柱状图两大核心组件,分别显示总流量、WiFi/移动数据分布以及每小时使用趋势。采用响应式编程框架管理数据状态,通过动态图表帮助用户直观了解流量消耗规律。设计上注重数据可视化效果,使用颜色编码区分数据类型,并支持触摸交互查看详细数值。整个页面采用统一的设计语言,包括圆角卡片、间距规范和颜
流量监控应用中,用户经常需要查看某一天的详细流量使用情况。日详情页面就是为了满足这个需求而设计的,它能够展示当天的总流量、WiFi和移动数据的分布,以及每小时的流量变化趋势。这个页面帮助用户深入了解自己的流量消费模式。
功能定位与设计思路
日详情页面从流量日历或者其他入口跳转过来,接收一个DailyUsage对象作为参数。页面需要完成以下几件事:
- 展示选中日期的总流量统计
- 分别显示WiFi和移动数据的使用量
- 用柱状图展示24小时内每个时段的流量分布
- 提供直观的数据可视化,帮助用户发现使用规律
页面整体结构
先看页面的基础框架搭建,这是整个页面的骨架:
class DailyDetailView extends GetView<DailyDetailController> {
const DailyDetailView({super.key});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.backgroundColor,
appBar: AppBar(
DailyDetailView继承GetView,通过controller属性访问控制器。Scaffold提供页面基础结构。
backgroundColor设置统一背景色,AppBar是顶部导航栏。
title: Obx(() => Text(controller.usage.value != null
? DateFormat('yyyy年MM月dd日').format(controller.usage.value!.date)
: '日详情')),
),
body: Obx(() => controller.usage.value == null
? const Center(child: Text('无数据'))
title用Obx包裹,根据usage数据动态显示日期或默认文字。DateFormat格式化日期为"年月日"格式。
body同样用Obx包裹,先判断数据是否为空,空则显示"无数据"提示。
: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildSummaryCard(),
SizedBox(height: 16.h),
_buildHourlyChart(),
],
),
)),
);
}
}
非空时使用SingleChildScrollView支持滚动,padding设置16.w内边距。
Column垂直排列汇总卡片和小时图表,SizedBox添加16.h间距。
Controller层数据管理
Controller负责接收页面参数和管理数据状态:
class DailyDetailController extends GetxController {
final usage = Rx<DailyUsage?>(null);
final hourlyData = <double>[].obs;
void onInit() {
super.onInit();
usage用Rx<DailyUsage?>声明,支持null值表示"还没有数据"。hourlyData是响应式double列表。
onInit在Controller创建后调用,用于初始化数据。
if (Get.arguments != null) {
usage.value = Get.arguments as DailyUsage;
}
loadHourlyData();
}
void loadHourlyData() {
hourlyData.value = List.generate(24, (i) => (50 + (i % 6) * 30).toDouble());
}
}
Get.arguments获取路由传参,做空判断后类型转换赋值给usage。loadHourlyData加载小时数据。
List.generate生成24个模拟数据,(i % 6) * 30让数据呈周期性变化,看起来更真实。
流量汇总卡片
这个卡片展示当天的流量概览:
Widget _buildSummaryCard() {
final usage = controller.usage.value!;
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16.r)),
child: Column(
从controller获取usage数据,!断言非空(前面已判断)。Container创建白色圆角卡片。
内边距20.w,圆角16.r,Column垂直排列内容。
children: [
Text('当日总流量', style: TextStyle(fontSize: 14.sp, color: AppTheme.textSecondary)),
SizedBox(height: 8.h),
Text(usage.formattedTotal, style: TextStyle(fontSize: 32.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 20.h),
"当日总流量"标签14sp次要颜色。formattedTotal是模型的格式化方法,返回如"1.5 GB"的字符串。
总流量32sp超大字号粗体,是视觉焦点。SizedBox添加间距。
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildItem('WiFi', usage.formattedWifi, AppTheme.wifiColor),
_buildItem('移动数据', usage.formattedMobile, AppTheme.mobileColor),
],
),
],
),
);
}
Row横向排列WiFi和移动数据,spaceAround均匀分布。_buildItem是封装的数据项组件。
WiFi用绿色,移动数据用橙色,颜色编码在整个App中保持一致。
数据项封装:
Widget _buildItem(String label, String value, Color color) {
return Column(
children: [
Text(label, style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary)),
SizedBox(height: 4.h),
Text(value, style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: color)),
],
);
}
_buildItem接收标签、数值、颜色三个参数。Column垂直排列标签和数值。
标签12sp次要颜色,数值16sp半粗体彩色。参数化设计方便复用。
每小时流量柱状图
这是页面的核心功能,用柱状图展示24小时的流量分布:
Widget _buildHourlyChart() {
return Container(
height: 200.h,
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16.r)),
child: Column(
Container固定高度200.h,内边距16.w,白色圆角背景。
Column垂直排列标题和图表。
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('每小时流量', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
SizedBox(height: 12.h),
Expanded(
child: Obx(() => BarChart(
crossAxisAlignment左对齐。标题14sp半粗体。SizedBox添加12.h间距。
Expanded让图表占据剩余空间,Obx响应hourlyData变化。BarChart是fl_chart库的柱状图组件。
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: controller.hourlyData.isEmpty ? 100 : controller.hourlyData.reduce((a, b) => a > b ? a : b) * 1.2,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
BarChartData配置图表数据。alignment设置柱子均匀分布。maxY动态计算,取最大值乘1.2留出顶部空间。
barTouchData启用触摸交互,点击柱子可以看到具体数值。
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() % 4 == 0) {
titlesData配置坐标轴标签。bottomTitles是X轴标签配置。
getTitlesWidget自定义标签显示,value.toInt() % 4 == 0只显示0、4、8、12、16、20时的标签,避免拥挤。
return Text('${value.toInt()}时', style: TextStyle(fontSize: 10.sp, color: AppTheme.textSecondary));
}
return const SizedBox();
},
),
),
显示的标签格式为"0时"、"4时"等,10sp小字号次要颜色。
不显示的位置返回空SizedBox。
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
gridData: FlGridData(show: false),
左、上、右三侧标签都隐藏,只保留底部X轴标签。borderData隐藏边框,gridData隐藏网格线。
简洁的设计让图表更清爽,重点突出柱子本身。
barGroups: List.generate(24, (index) {
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: index < controller.hourlyData.length ? controller.hourlyData[index] : 0,
barGroups生成24个柱子。BarChartGroupData定义每组柱子,x是索引。
BarChartRodData定义单个柱子,toY是柱子高度,从hourlyData获取对应值。
color: AppTheme.primaryColor,
width: 8.w,
borderRadius: BorderRadius.vertical(top: Radius.circular(4.r)),
),
],
);
}),
),
)),
),
],
),
);
}
柱子颜色使用主色调,宽度8.w,顶部圆角4.r底部直角。
这种设计让柱子看起来更有质感,符合柱状图的视觉习惯。
数据模型设计
DailyUsage模型包含日期和各类流量数据:
class DailyUsage {
final DateTime date;
final int totalUsage;
final int wifiUsage;
final int mobileUsage;
DailyUsage({
required this.date,
DailyUsage定义日详情数据结构。date是日期,totalUsage是总流量字节数。
wifiUsage和mobileUsage分别是WiFi和移动数据的字节数。required确保必须传入。
required this.totalUsage,
required this.wifiUsage,
required this.mobileUsage,
});
String get formattedTotal => _formatBytes(totalUsage);
String get formattedWifi => _formatBytes(wifiUsage);
String get formattedMobile => _formatBytes(mobileUsage);
构造函数接收所有必需参数。getter方法返回格式化后的字符串。
用getter而不是普通方法,调用时不需要括号,代码更简洁。
String _formatBytes(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
}
}
_formatBytes根据字节数大小自动选择单位。小于1KB显示B,小于1MB显示KB,小于1GB显示MB,否则显示GB。
toStringAsFixed控制小数位数,KB保留1位,MB和GB保留2位。
页面跳转与参数传递
从日历页面跳转到日详情:
void onDaySelected(DailyUsage usage) {
Get.toNamed(Routes.DAILY_DETAIL, arguments: usage);
}
Get.toNamed跳转到日详情页面,arguments传递DailyUsage对象。
GetX的路由传参非常简洁,可以传递任意类型的对象。
写在最后
日详情页面虽然功能相对简单,但它是流量监控应用中非常重要的一环。通过清晰的数据展示和直观的图表可视化,帮助用户深入了解自己的流量使用习惯。
在实现过程中,我们用到了GetX的状态管理和路由传参、fl_chart的柱状图绑制、flutter_screenutil的屏幕适配等技术。这些技术的组合使用,让我们能够快速构建出功能完善、体验流畅的页面。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)