流量日历是一个非常实用的功能,它以日历的形式展示每天的流量使用情况。用户可以直观地看到哪些天用得多、哪些天用得少,从而发现自己的流量使用规律。这个页面使用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核心参数解析

  • firstDaylastDay:定义日历可选范围,这里设置从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库实现日历功能
  • 通过CalendarStyleHeaderStyle自定义日历外观
  • 使用Map存储日期与流量的映射关系
  • 日期比较时注意去除时分秒信息
  • 可以通过calendarBuilders添加流量标记

这个功能帮助用户发现自己的流量使用规律,比如周末是否用得更多、月初月末的使用差异等。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐