流量监控应用中,用户经常需要查看某一天的详细流量使用情况。日详情页面就是为了满足这个需求而设计的,它能够展示当天的总流量、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

Logo

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

更多推荐