Flutter for OpenHarmony移动数据使用监管助手App实战 - 流量日历实现
流量日历是一个非常实用的功能,它以日历的形式展示每天的流量使用情况。用户可以直观地看到哪些天用得多、哪些天用得少,从而发现自己的流量使用规律。这个页面使用库实现日历功能,配合GetX进行状态管理。
流量日历是一个非常实用的功能,它以日历的形式展示每天的流量使用情况。用户可以直观地看到哪些天用得多、哪些天用得少,从而发现自己的流量使用规律。这个页面使用table_calendar库实现日历功能,配合GetX进行状态管理。
功能入口
流量日历可以从统计页面进入:
ListTile(
leading: Icon(Icons.calendar_month, color: AppTheme.primaryColor),
title: const Text('流量日历'),
subtitle: const Text('查看每日流量使用'),
trailing: const Icon(Icons.chevron_right),
onTap: () => Get.toNamed(Routes.DATA_CALENDAR),
)
点击后跳转到/data-calendar路由,进入日历页面。
页面整体结构
流量日历页面采用Column布局,从上到下分为三个部分:
class DataCalendarView extends GetView<DataCalendarController> {
const DataCalendarView({super.key});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.backgroundColor,
appBar: AppBar(title: const Text('流量日历')),
body: Column(
children: [
_buildCalendar(),
SizedBox(height: 16.h),
_buildSelectedDayInfo(),
],
),
);
}
}
页面结构说明:
- AppBar:标准导航栏,显示"流量日历"标题
- 日历组件:占据页面主要区域,可以切换月份、点击选择日期
- 选中日期信息卡片:展示选中日期的流量详情
使用GetView<DataCalendarController>可以自动获取Controller实例,无需手动调用Get.find()。
日历组件实现
日历是这个页面的核心组件,使用table_calendar库实现:
Widget _buildCalendar() {
return Container(
margin: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: controller.focusedDate.value,
selectedDayPredicate: (day) => isSameDay(controller.selectedDate.value, day),
onDaySelected: controller.onDaySelected,
calendarStyle: CalendarStyle(
selectedDecoration: const BoxDecoration(
color: AppTheme.primaryColor,
shape: BoxShape.circle,
),
todayDecoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.3),
shape: BoxShape.circle,
),
),
headerStyle: HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
titleTextStyle: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
)),
);
}
TableCalendar核心参数解析:
firstDay和lastDay:定义日历可选范围,这里设置从2020年到2030年,覆盖足够长的时间跨度focusedDay:当前聚焦的日期,决定日历显示哪个月份selectedDayPredicate:判断某天是否被选中的回调函数,返回true则该日期显示选中状态onDaySelected:用户点击日期时的回调,接收选中日期和聚焦日期两个参数
CalendarStyle样式定制:
selectedDecoration:选中日期的装饰,使用主题色圆形背景todayDecoration:今天的装饰,使用半透明主题色背景,与选中状态区分
HeaderStyle头部样式:
formatButtonVisible: false:隐藏格式切换按钮,不需要周视图/月视图切换titleCentered: true:月份标题居中显示titleTextStyle:自定义标题字体大小和粗细
整个日历组件包裹在白色圆角容器中,与页面背景形成层次感。使用Obx包裹确保日期变化时UI自动更新。
选中日期信息卡片
当用户选中某一天后,下方卡片会显示该日期的详细流量信息:
Widget _buildSelectedDayInfo() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() {
final usage = controller.getUsageForDate(controller.selectedDate.value);
return Column(
children: [
Text(
DateFormat('yyyy年MM月dd日').format(controller.selectedDate.value),
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 12.h),
Text(
controller.formatBytes(usage),
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
SizedBox(height: 4.h),
Text(
'当日流量使用',
style: TextStyle(
fontSize: 12.sp,
color: AppTheme.textSecondary,
),
),
],
);
}),
);
}
卡片内容说明:
- 日期显示:使用
intl包的DateFormat将日期格式化为"2024年01月15日"这样的中文格式 - 流量数值:大号字体突出显示,使用主题色增强视觉效果
- 说明文字:小号灰色字体,说明数值含义
Obx包裹整个Column,当selectedDate变化时,会自动调用getUsageForDate获取新日期的流量数据并更新显示。
Controller层完整实现
Controller负责管理日历的状态和数据:
import 'package:get/get.dart';
class DataCalendarController extends GetxController {
final selectedDate = DateTime.now().obs;
final focusedDate = DateTime.now().obs;
final dailyUsageMap = <DateTime, int>{}.obs;
void onInit() {
super.onInit();
loadCalendarData();
}
void loadCalendarData() {
final now = DateTime.now();
for (int i = 0; i < 30; i++) {
final date = DateTime(now.year, now.month, now.day - i);
dailyUsageMap[date] = (500 + (i % 10) * 100) * 1024 * 1024;
}
}
void onDaySelected(DateTime selected, DateTime focused) {
selectedDate.value = selected;
focusedDate.value = focused;
}
int getUsageForDate(DateTime date) {
final key = DateTime(date.year, date.month, date.day);
return dailyUsageMap[key] ?? 0;
}
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';
}
}
状态变量说明:
selectedDate:当前选中的日期,初始值为今天focusedDate:日历聚焦的日期,决定显示哪个月dailyUsageMap:存储每天流量数据的Map,key是日期,value是字节数
loadCalendarData方法:
这个方法在Controller初始化时调用,加载最近30天的流量数据。实际项目中应该从本地数据库或服务器获取真实数据,这里用模拟数据演示。
注意创建日期时只保留年月日:DateTime(now.year, now.month, now.day - i),这样可以确保Map的key不包含时分秒,方便后续查询。
onDaySelected方法:
用户点击日期时调用,同时更新选中日期和聚焦日期。table_calendar库要求同时维护这两个状态,focusedDay用于控制日历显示哪个月。
getUsageForDate方法:
根据日期查询流量数据。这里有个关键点:传入的日期可能包含时分秒信息,需要先转换成只有年月日的DateTime对象,才能正确匹配Map中的key。
formatBytes方法:
将字节数转换为人类可读的格式。根据数值大小自动选择合适的单位(B、KB、MB、GB),并保留适当的小数位数。
路由配置
在路由表中注册流量日历页面:
GetPage(
name: Routes.DATA_CALENDAR,
page: () => const DataCalendarView(),
binding: DataCalendarBinding(),
)
对应的Binding类负责注入Controller:
class DataCalendarBinding extends Bindings {
void dependencies() {
Get.lazyPut<DataCalendarController>(() => DataCalendarController());
}
}
使用lazyPut延迟创建Controller,只有在页面真正需要时才会实例化,节省内存。
日历标记功能扩展
如果想在日历上标记有数据的日期,可以使用calendarBuilders参数自定义日期单元格:
TableCalendar(
// ... 其他参数
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
final usage = controller.getUsageForDate(date);
if (usage > 0) {
return Positioned(
bottom: 1,
child: Container(
width: 6.w,
height: 6.w,
decoration: BoxDecoration(
color: _getUsageColor(usage),
shape: BoxShape.circle,
),
),
);
}
return null;
},
),
)
markerBuilder可以在日期下方添加小圆点标记,根据流量大小显示不同颜色,让用户一眼就能看出哪些天流量用得多。
颜色判断逻辑可以这样实现:
Color _getUsageColor(int bytes) {
final mb = bytes / (1024 * 1024);
if (mb > 1000) return Colors.red;
if (mb > 500) return Colors.orange;
return Colors.green;
}
流量超过1GB显示红色,500MB到1GB显示橙色,500MB以下显示绿色。
月份切换回调
如果需要在用户切换月份时加载对应月份的数据,可以使用onPageChanged回调:
TableCalendar(
// ... 其他参数
onPageChanged: (focusedDay) {
controller.focusedDate.value = focusedDay;
controller.loadMonthData(focusedDay);
},
)
Controller中添加按月加载数据的方法:
void loadMonthData(DateTime month) {
final firstDay = DateTime(month.year, month.month, 1);
final lastDay = DateTime(month.year, month.month + 1, 0);
// 加载该月份的流量数据
// 实际项目中这里应该调用数据服务
}
这样可以实现按需加载,避免一次性加载过多数据。
依赖包说明
流量日历功能需要以下依赖:
dependencies:
table_calendar: ^3.0.9
intl: ^0.18.1
table_calendar:功能强大的日历组件,支持多种视图和自定义样式intl:国际化支持,用于日期格式化
这两个包都兼容OpenHarmony平台,可以正常使用。
小结
流量日历通过日历视图让用户直观地查看历史流量数据,主要技术点包括:
- 使用
table_calendar库实现日历功能 - 通过
CalendarStyle和HeaderStyle自定义日历外观 - 使用Map存储日期与流量的映射关系
- 日期比较时注意去除时分秒信息
- 可以通过
calendarBuilders添加流量标记
这个功能帮助用户发现自己的流量使用规律,比如周末是否用得更多、月初月末的使用差异等。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)