用户在应用列表中点击某个App后,会进入应用详情页面。这个页面展示单个应用的详细流量使用情况,包括WiFi和移动数据的分别统计、近期使用趋势图、以及后台流量控制开关。
请添加图片描述

页面设计思路

应用详情页面要回答用户几个核心问题:

  • 这个App总共用了多少流量?
  • WiFi和移动数据各占多少?
  • 最近几天的使用趋势是什么样的?
  • 能不能限制它的后台流量?

数据传递方式

从应用列表跳转到详情页时,需要传递应用信息:

// 列表页跳转时传参
Get.toNamed(Routes.APP_DETAIL, arguments: appUsage);

// 详情页接收参数
class AppDetailController extends GetxController {
  final app = Rx<AppUsage?>(null);

Get.toNamed跳转并传递参数。
arguments可以传递任意类型的对象。
Rx<AppUsage?>存储可为null的应用数据。

  
  void onInit() {
    super.onInit();
    if (Get.arguments != null) {
      app.value = Get.arguments as AppUsage;
    }
    loadWeeklyData();
  }
}

onInit在控制器初始化时调用。
Get.arguments获取传递的参数。
loadWeeklyData加载近7天的数据。

页面整体结构

首先定义应用详情页面的基本框架:

class AppDetailView extends GetView<AppDetailController> {
  const AppDetailView({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(

继承GetView自动注入AppDetailController控制器。
const构造函数优化widget重建性能。
build方法返回页面的完整UI结构。

      backgroundColor: AppTheme.backgroundColor,
      appBar: AppBar(
        title: Obx(() => Text(controller.app.value?.appName ?? '应用详情')),
        actions: [
          IconButton(

Scaffold提供Material Design页面框架。
Obx监听app变化动态显示应用名称。
AppBar右侧放置更多操作按钮。

            icon: Icon(Icons.more_vert),
            onPressed: () => _showMoreOptions(),
          ),
        ],
      ),
      body: Obx(() => controller.app.value == null
          ? const Center(child: Text('无数据'))

more_vert图标表示更多操作。
Obx监听app状态显示不同内容。
app为null时显示无数据提示。

          : SingleChildScrollView(
              padding: EdgeInsets.all(16.w),
              child: Column(
                children: [
                  _buildAppHeader(),

SingleChildScrollView让内容可滚动。
Column垂直排列四个区域。
_buildAppHeader构建应用信息头部。

                  SizedBox(height: 16.h),
                  _buildUsageCard(),
                  SizedBox(height: 16.h),
                  _buildChart(),
                  SizedBox(height: 16.h),
                  _buildSettings(),

_buildUsageCard构建流量统计卡片。
_buildChart构建趋势图表。
_buildSettings构建设置选项。

                ],
              ),
            )),
    );
  }
}

16.h间距让各区域视觉分隔清晰。
闭合所有括号完成页面结构。
整体布局清晰直观。

应用信息头部

展示应用图标、名称和包名:

Widget _buildAppHeader() {
  final app = controller.app.value!;
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white,

获取当前应用数据。
Container作为头部的容器。
20.w内边距让内容更宽松。

      borderRadius: BorderRadius.circular(16.r),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.03),
          blurRadius: 10.r,
          offset: Offset(0, 4.h),
        ),
      ],

16.r圆角保持视觉一致。
轻微阴影让卡片有悬浮感。
透明度0.03的阴影非常柔和。

    ),
    child: Row(
      children: [
        Container(
          width: 64.w,
          height: 64.w,
          decoration: BoxDecoration(

Row横向排列图标和信息。
图标容器尺寸64.w比列表项更大。
BoxDecoration定义容器样式。

            color: AppTheme.primaryColor.withOpacity(0.1),
            borderRadius: BorderRadius.circular(16.r),
          ),
          child: Icon(Icons.apps, size: 36.sp, color: AppTheme.primaryColor),
        ),
        SizedBox(width: 16.w),

浅色背景让图标区域柔和。
16.r圆角让容器更圆润。
间距16.w让图标和信息不挤。

        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                app.appName,
                style: TextStyle(

Expanded让信息区域占据剩余空间。
Column垂直排列名称、包名、最后使用时间。
crossAxisAlignment让内容左对齐。

                  fontSize: 20.sp,
                  fontWeight: FontWeight.bold,
                  color: AppTheme.textPrimary,
                ),
              ),
              SizedBox(height: 6.h),

应用名称用20.sp大字号加粗。
主要文字颜色确保可读性。
间距6.h后显示包名。

              Text(
                app.packageName,
                style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary),
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),

包名用12.sp小字号。
maxLines: 1限制单行显示。
overflow处理文字溢出。

              SizedBox(height: 4.h),
              Text(
                '最后使用: ${_formatLastUsed(app.lastUsed)}',
                style: TextStyle(fontSize: 11.sp, color: AppTheme.textSecondary),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

显示最后使用时间。
_formatLastUsed格式化为相对时间。
整体设计信息层次分明。

格式化最后使用时间

将时间转换为相对时间显示:

String _formatLastUsed(DateTime dateTime) {
  final now = DateTime.now();
  final diff = now.difference(dateTime);
  if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';

计算当前时间与最后使用时间的差值。
小于60分钟显示"X分钟前"。
相对时间比绝对时间更直观。

  if (diff.inHours < 24) return '${diff.inHours}小时前';
  return '${diff.inDays}天前';
}

小于24小时显示"X小时前"。
否则显示"X天前"。
简洁的时间格式化逻辑。

流量统计卡片

展示WiFi、移动数据和总计三个数据:

Widget _buildUsageCard() {
  final app = controller.app.value!;
  return Container(
    padding: EdgeInsets.all(20.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
    ),

获取当前应用数据。
Container作为统计卡片的容器。
白色背景与页面灰色背景对比。

    child: Column(
      children: [
        Row(
          children: [
            Expanded(
              child: _buildUsageItem('WiFi', app.formattedWifi, AppTheme.wifiColor, Icons.wifi),
            ),

Column垂直排列分项和总计。
Row横向排列WiFi和移动数据。
_buildUsageItem构建单个统计项。

            Container(width: 1, height: 60.h, color: Colors.grey.shade200),
            Expanded(
              child: _buildUsageItem('移动数据', app.formattedMobile, AppTheme.mobileColor, Icons.signal_cellular_alt),
            ),
          ],
        ),

竖线分隔WiFi和移动数据。
60.h的高度适中。
Expanded让两项平分宽度。

        SizedBox(height: 16.h),
        Divider(height: 1),
        SizedBox(height: 16.h),
        _buildTotalUsage(app),
      ],
    ),
  );
}

间距16.h后显示分隔线。
Divider水平分隔分项和总计。
_buildTotalUsage构建总计显示。

统计项组件

单个流量统计项的显示:

Widget _buildUsageItem(String label, String value, Color color, IconData icon) {
  return Column(
    children: [
      Container(
        width: 44.w,
        height: 44.w,

接收标签、数值、颜色、图标四个参数。
Column垂直排列图标、标签、数值。
图标容器尺寸44.w。

        decoration: BoxDecoration(
          color: color.withOpacity(0.1),
          shape: BoxShape.circle,
        ),
        child: Icon(icon, color: color, size: 24.sp),
      ),
      SizedBox(height: 10.h),

浅色背景让图标区域柔和。
BoxShape.circle创建圆形容器。
间距10.h后显示标签。

      Text(label, style: TextStyle(fontSize: 13.sp, color: AppTheme.textSecondary)),
      SizedBox(height: 4.h),
      Text(
        value,
        style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: color),
      ),
    ],
  );
}

标签用13.sp小字号。
数值用18.sp大字号加粗。
颜色参数让不同项有不同颜色。

总计显示

显示总流量:

Widget _buildTotalUsage(AppUsage app) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Text('总计使用 ', style: TextStyle(fontSize: 14.sp, color: AppTheme.textSecondary)),

Row居中排列标签和数值。
标签"总计使用"用14.sp字号。
次要颜色作为说明文字。

      Text(
        app.formattedTotal,
        style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: AppTheme.primaryColor),
      ),
    ],
  );
}

总流量用24.sp超大字号加粗。
主色调突出显示总流量。
整体设计让总流量最醒目。

趋势图表

用折线图展示近7天的流量趋势:

Widget _buildChart() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
    ),

Container作为图表的容器。
白色背景与页面灰色背景对比。
16.r圆角保持视觉一致。

    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '近7天使用趋势',
          style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: AppTheme.textPrimary),
        ),

Column垂直排列标题和图表。
标题用16.sp字号,w600加粗。
crossAxisAlignment让标题左对齐。

        SizedBox(height: 20.h),
        SizedBox(
          height: 160.h,
          child: Obx(() => LineChart(
            LineChartData(

间距20.h后显示图表。
固定高度160.h的图表容器。
LineChart是fl_chart库的折线图组件。

              gridData: FlGridData(
                show: true,
                drawVerticalLine: false,
                horizontalInterval: 0.5,
                getDrawingHorizontalLine: (value) => FlLine(color: Colors.grey.shade200, strokeWidth: 1),
              ),

gridData配置网格线。
只显示水平网格线。
horizontalInterval设置网格间隔。

              titlesData: FlTitlesData(
                leftTitles: AxisTitles(
                  sideTitles: SideTitles(
                    showTitles: true,
                    reservedSize: 40.w,

titlesData配置坐标轴标签。
leftTitles配置左侧Y轴标签。
reservedSize预留标签空间。

                    getTitlesWidget: (value, meta) => Text(
                      '${value.toStringAsFixed(1)}G',
                      style: TextStyle(fontSize: 10.sp, color: AppTheme.textSecondary),
                    ),
                  ),
                ),

显示流量数值,单位G。
10.sp小字号适合坐标轴标签。
次要颜色让标签不会太突出。

                bottomTitles: AxisTitles(
                  sideTitles: SideTitles(
                    showTitles: true,
                    getTitlesWidget: (value, meta) {
                      final days = ['一', '二', '三', '四', '五', '六', '日'];

bottomTitles配置底部X轴标签。
days数组存储星期的中文简写。
getTitlesWidget返回标签组件。

                      final index = value.toInt();
                      if (index >= 0 && index < days.length) {
                        return Text(days[index], style: TextStyle(fontSize: 10.sp, color: AppTheme.textSecondary));
                      }
                      return const Text('');
                    },

根据索引获取星期文字。
边界检查避免数组越界。
无效索引返回空文字。

                  ),
                ),
                topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
                rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
              ),

隐藏顶部和右侧标签。
让图表更简洁。
只显示必要的坐标轴信息。

              borderData: FlBorderData(show: false),
              lineBarsData: [
                LineChartBarData(
                  spots: List.generate(
                    controller.weeklyData.length,
                    (i) => FlSpot(i.toDouble(), controller.weeklyData[i]),
                  ),

隐藏边框让图表更简洁。
lineBarsData定义折线数据。
List.generate生成数据点。

                  isCurved: true,
                  curveSmoothness: 0.3,
                  color: AppTheme.primaryColor,
                  barWidth: 3,

isCurved启用曲线。
curveSmoothness控制曲线平滑度。
barWidth设置线条宽度。

                  dotData: FlDotData(
                    show: true,
                    getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter(
                      radius: 4,
                      color: Colors.white,
                      strokeWidth: 2,
                      strokeColor: AppTheme.primaryColor,
                    ),
                  ),

dotData配置数据点样式。
空心圆点,白色填充加主色描边。
radius: 4设置点的大小。

                  belowBarData: BarAreaData(
                    show: true,
                    color: AppTheme.primaryColor.withOpacity(0.1),
                  ),
                ),
              ],
              minY: 0,
            ),
          )),
        ),
      ],
    ),
  );
}

belowBarData配置曲线下方填充。
浅色填充增加视觉层次感。
minY: 0确保Y轴从0开始。

设置选项

提供后台流量控制等设置:

Widget _buildSettings() {
  return Container(
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
    ),
    child: Column(

Container作为设置区域的容器。
白色背景与页面灰色背景对比。
Column垂直排列设置项。

      children: [
        Obx(() => SwitchListTile(
          title: Text('允许后台流量', style: TextStyle(fontSize: 15.sp, color: AppTheme.textPrimary)),
          subtitle: Text('关闭后该应用将无法在后台使用流量', style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary)),

SwitchListTile是带开关的列表项。
title显示设置项名称。
subtitle显示说明文字。

          value: controller.backgroundDataEnabled.value,
          onChanged: (v) => controller.toggleBackgroundData(),
          activeColor: AppTheme.primaryColor,
        )),
        Divider(height: 1, indent: 16.w, endIndent: 16.w),

value绑定开关状态。
onChanged切换后台流量权限。
Divider分隔各设置项。

        ListTile(
          title: Text('设置流量限额', style: TextStyle(fontSize: 15.sp, color: AppTheme.textPrimary)),
          subtitle: Text('限制该应用的每日流量使用', style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary)),
          trailing: Icon(Icons.chevron_right, color: AppTheme.textSecondary),

ListTile是普通列表项。
trailing显示右箭头表示可点击。
点击弹出限额设置对话框。

          onTap: () => _showLimitDialog(),
        ),
        Divider(height: 1, indent: 16.w, endIndent: 16.w),
        ListTile(
          title: Text('清除流量统计', style: TextStyle(fontSize: 15.sp, color: Colors.red)),
          onTap: () => _showClearConfirmDialog(),
        ),
      ],
    ),
  );
}

清除统计用红色文字提示危险操作。
点击需要二次确认。
闭合Column和Container完成设置区域。

Controller实现

控制器管理应用详情的状态:

class AppDetailController extends GetxController {
  final app = Rx<AppUsage?>(null);
  final weeklyData = <double>[].obs;
  final backgroundDataEnabled = true.obs;
  final usageLimit = 0.0.obs;

app存储当前应用数据。
weeklyData存储近7天的流量数据。
backgroundDataEnabled控制后台流量开关。

  
  void onInit() {
    super.onInit();
    if (Get.arguments != null) {
      app.value = Get.arguments as AppUsage;
      loadSettings();
    }
    loadWeeklyData();
  }

onInit初始化时获取传递的参数。
loadSettings加载该应用的设置。
loadWeeklyData加载近7天数据。

  void loadWeeklyData() {
    weeklyData.value = [0.5, 0.8, 0.6, 1.2, 0.9, 0.7, 1.0];
  }

  void toggleBackgroundData() {
    backgroundDataEnabled.value = !backgroundDataEnabled.value;
    saveSettings();
  }
}

模拟近7天的流量数据。
toggleBackgroundData切换后台流量状态。
saveSettings保存设置到本地。

写在最后

应用详情页面让用户深入了解单个应用的流量使用情况。通过清晰的数据展示、直观的趋势图表、便捷的控制选项,帮助用户管理应用的流量消耗。

可以继续优化的方向:

  • 支持查看更长时间范围的数据
  • 添加流量使用预测功能
  • 支持设置多个提醒阈值
  • 对比不同时间段的使用情况

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

Logo

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

更多推荐