流量排行页面展示应用的流量使用排名,帮助用户快速找出"流量大户"。这个功能对于流量有限的用户特别有用,可以针对性地管理高耗流量的应用。

功能设计

流量排行页面的核心功能:

  • 按流量使用量从高到低排列应用
  • 支持切换时间范围(今日、本周、本月)
  • 显示排名、应用名称、流量使用量
  • 前三名用特殊样式突出显示
  • 点击应用可以进入详情页

页面整体结构

首先定义页面类,继承GetView实现控制器自动注入:

class DataRankingView extends GetView<DataRankingController> {
  const DataRankingView({super.key});

这里使用GetX框架的GetView基类,泛型参数指定控制器类型。
const构造函数可以让Flutter在重建时复用widget实例,提升性能。
super.key用于传递widget的唯一标识符。

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppTheme.backgroundColor,
      appBar: AppBar(

build方法是widget的核心,返回页面的UI结构。
Scaffold提供Material Design的基础页面框架。
backgroundColor使用统一的主题背景色,保持视觉一致性。

        title: const Text('流量排行'),
        actions: [
          IconButton(
            icon: Icon(Icons.info_outline),
            onPressed: () => _showRankingInfo(),

AppBar的title设置页面标题为"流量排行"。
actions数组放置右侧的操作按钮。
info按钮点击后弹出排行规则说明,提升用户体验。

          ),
        ],
      ),
      body: Column(
        children: [
          _buildPeriodSelector(),
          Expanded(child: _buildRankingList()),

body是页面主体内容区域。
Column垂直排列子组件,先放时间选择器,再放排行列表。
Expanded让列表占据剩余空间,自适应屏幕高度。

        ],
      ),
    );
  }
}

闭合Column、Scaffold和类定义。
整体结构简洁清晰,职责分明。
时间选择器和列表分别封装成独立方法,便于维护。

时间段选择器

时间选择器让用户切换查看不同时间范围的排行:

Widget _buildPeriodSelector() {
  final periods = ['今日', '本周', '本月'];
  
  return Container(
    margin: EdgeInsets.all(16.w),

定义三个时间选项的文本数组。
Container作为外层容器,设置统一的外边距。
16.w使用flutter_screenutil的适配单位,自动适配不同屏幕。

    padding: EdgeInsets.all(4.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),

内边距4.w给选中态背景留出空间。
BoxDecoration定义容器的装饰样式。
白色背景配合12.r的圆角,现代感十足。

      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.03),
          blurRadius: 8.r,
          offset: Offset(0, 2.h),

boxShadow添加轻微阴影,让选择器有悬浮感。
透明度0.03的黑色阴影非常柔和,不会太突兀。
offset设置阴影向下偏移2.h,模拟光源从上方照射。

        ),
      ],
    ),
    child: Row(
      children: List.generate(periods.length, (index) {
        return Expanded(

Row横向排列三个选项按钮。
List.generate动态生成指定数量的子组件。
Expanded让每个选项平分宽度,保持均匀分布。

          child: GestureDetector(
            onTap: () => controller.changePeriod(index),
            child: Obx(() => AnimatedContainer(
              duration: const Duration(milliseconds: 200),

GestureDetector处理点击事件,调用控制器切换时间段。
Obx是GetX的响应式包装器,监听状态变化自动重建。
AnimatedContainer在属性变化时自动执行动画,时长200毫秒。

              padding: EdgeInsets.symmetric(vertical: 12.h),
              decoration: BoxDecoration(
                color: controller.selectedPeriod.value == index
                    ? AppTheme.primaryColor
                    : Colors.transparent,

垂直内边距12.h让按钮有足够的点击区域。
选中项使用主色背景,未选中项透明背景。
这种分段控件样式在iOS上很常见,用户容易理解。

                borderRadius: BorderRadius.circular(10.r),
              ),
              child: Center(
                child: Text(
                  periods[index],

选中态圆角10.r比外层12.r稍小,形成层次感。
Center让文字在容器中居中显示。
从periods数组取出对应索引的文本。

                  style: TextStyle(
                    fontSize: 14.sp,
                    fontWeight: FontWeight.w600,
                    color: controller.selectedPeriod.value == index
                        ? Colors.white
                        : AppTheme.textSecondary,

字号14.sp适中,不会太大也不会太小。
w600的字重让文字稍微加粗,更醒目。
选中时白色文字,未选中时使用次要文字颜色。

                  ),
                ),
              ),
            )),
          ),
        );
      }),
    ),
  );
}

闭合所有嵌套的括号和方法定义。
整个选择器实现了平滑的切换动画效果。
代码结构清晰,每个属性的作用一目了然。

排行列表

排行列表展示应用的流量使用排名:

Widget _buildRankingList() {
  return Obx(() {
    if (controller.isLoading.value) {
      return const Center(child: CircularProgressIndicator());
    }

Obx监听isLoading和rankingList两个响应式变量。
加载中时显示居中的转圈动画。
const修饰符优化性能,避免重复创建相同的widget。

    if (controller.rankingList.isEmpty) {
      return _buildEmptyState();
    }
    
    return ListView.builder(
      padding: EdgeInsets.symmetric(horizontal: 16.w),

空数据时显示占位图,提升用户体验。
ListView.builder采用懒加载模式,只渲染可见区域的item。
水平padding让列表内容与屏幕边缘保持距离。

      itemCount: controller.rankingList.length,
      itemBuilder: (context, index) {
        final app = controller.rankingList[index];
        return _buildRankingItem(app, index);
      },
    );
  });
}

itemCount指定列表项总数。
itemBuilder回调函数构建每个列表项。
将app对象和索引传给_buildRankingItem方法。

排行项组件

每个排行项展示应用的排名和流量信息:

Widget _buildRankingItem(AppUsage app, int index) {
  final isTopThree = index < 3;
  
  return GestureDetector(
    onTap: () => Get.toNamed(Routes.APP_DETAIL, arguments: app),

isTopThree判断是否为前三名,用于特殊样式。
GestureDetector处理点击事件,跳转到应用详情页。
arguments传递app对象供详情页使用。

    child: Container(
      margin: EdgeInsets.only(bottom: 10.h),
      padding: EdgeInsets.all(14.w),
      decoration: BoxDecoration(
        color: Colors.white,

底部margin让item之间有呼吸感。
内边距14.w让内容不会贴边。
白色背景与灰色页面背景形成对比。

        borderRadius: BorderRadius.circular(14.r),
        border: isTopThree
            ? Border.all(color: _getRankColor(index).withOpacity(0.3), width: 1.5)
            : null,

圆角14.r让卡片更圆润。
前三名添加彩色边框高亮显示。
边框宽度1.5比默认的1稍粗,更醒目。

        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.02),
            blurRadius: 6.r,
            offset: Offset(0, 2.h),
          ),
        ],

轻微阴影让卡片有悬浮感。
透明度0.02非常淡,不会喧宾夺主。
阴影向下偏移,符合自然光照效果。

      ),
      child: Row(
        children: [
          _buildRankBadge(index),
          SizedBox(width: 12.w),

Row横向排列排名徽章、图标、信息和使用量。
SizedBox作为间距组件,比Container更轻量。
12.w的间距让各元素之间不会太挤。

          _buildAppIcon(app),
          SizedBox(width: 12.w),
          Expanded(child: _buildAppInfo(app)),
          _buildUsageInfo(app),
        ],
      ),
    ),
  );
}

Expanded让应用信息区域自适应宽度。
使用量信息放在最右侧,方便用户快速查看。
各组件封装成独立方法,代码更清晰。

排名徽章

排名徽章用不同颜色和图标区分名次:

Widget _buildRankBadge(int index) {
  final isTopThree = index < 3;
  final color = _getRankColor(index);
  
  return Container(
    width: 32.w,
    height: 32.w,

判断是否为前三名,获取对应颜色。
徽章尺寸32.w是经过测试的最佳大小。
太大会喧宾夺主,太小看不清图标。

    decoration: BoxDecoration(
      color: isTopThree ? color : Colors.grey.shade100,
      borderRadius: BorderRadius.circular(8.r),
      boxShadow: isTopThree
          ? [BoxShadow(color: color.withOpacity(0.3), blurRadius: 4.r, offset: Offset(0, 2.h))]
          : null,

前三名用彩色背景,其他用灰色背景。
圆角8.r让徽章看起来更柔和。
前三名添加发光效果,颜色与背景一致。

    ),
    child: Center(
      child: isTopThree
          ? Icon(_getRankIcon(index), size: 18.sp, color: Colors.white)
          : Text(
              '${index + 1}',

Center让内容在容器中居中。
前三名显示奖杯/奖牌/勋章图标。
其他名次显示数字,index从0开始所以要加1。

              style: TextStyle(
                fontSize: 14.sp,
                fontWeight: FontWeight.bold,
                color: AppTheme.textSecondary,
              ),
            ),
    ),
  );
}

数字用14.sp字号,加粗显示。
次要颜色让数字不会太突出。
整体设计让排名一目了然。

排名颜色和图标

定义前三名的颜色和图标:

Color _getRankColor(int index) {
  switch (index) {
    case 0:
      return const Color(0xFFFFD700); // 金色
    case 1:
      return const Color(0xFFC0C0C0); // 银色

switch语句根据索引返回对应颜色。
第一名金色,使用标准的金色色值。
第二名银色,经典的奖牌配色。

    case 2:
      return const Color(0xFFCD7F32); // 铜色
    default:
      return AppTheme.primaryColor;
  }
}

第三名铜色,完成金银铜三色组合。
default返回主色调,用于其他名次。
这是国际通用的奖牌配色,用户无需学习。

IconData _getRankIcon(int index) {
  switch (index) {
    case 0:
      return Icons.emoji_events; // 奖杯
    case 1:
      return Icons.workspace_premium; // 奖牌

第一名用奖杯图标,最高荣誉的象征。
第二名用奖牌图标,次于奖杯。
Material Icons提供丰富的图标选择。

    case 2:
      return Icons.military_tech; // 勋章
    default:
      return Icons.circle;
  }
}

第三名用勋章图标,也是荣誉的象征。
default返回圆形图标,实际上不会用到。
三种图标各有特色,增加页面趣味性。

应用图标和信息

展示应用的图标和详细信息:

Widget _buildAppIcon(AppUsage app) {
  return Container(
    width: 44.w,
    height: 44.w,
    decoration: BoxDecoration(
      color: AppTheme.primaryColor.withOpacity(0.1),
      borderRadius: BorderRadius.circular(12.r),

图标容器44.w与系统应用图标大小接近。
浅色背景让图标区域更明显。
圆角12.r比较圆润,符合现代UI趋势。

    ),
    child: Icon(Icons.apps, color: AppTheme.primaryColor, size: 26.sp),
  );
}

Widget _buildAppInfo(AppUsage app) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,

使用apps图标作为占位,实际项目中应显示真实图标。
Column垂直排列应用名和流量标签。
crossAxisAlignment设置左对齐。

    children: [
      Text(
        app.appName,
        style: TextStyle(
          fontSize: 15.sp,
          fontWeight: FontWeight.w600,
          color: AppTheme.textPrimary,

应用名用15.sp字号,稍大一些更醒目。
w600字重让名称加粗显示。
主要文字颜色确保可读性。

        ),
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
      ),
      SizedBox(height: 4.h),
      Row(
        children: [

maxLines限制单行显示,避免布局溢出。
ellipsis在文字过长时显示省略号。
Row横向排列WiFi和移动数据标签。

          _buildMiniTag('WiFi', app.formattedWifi, AppTheme.wifiColor),
          SizedBox(width: 8.w),
          _buildMiniTag('移动', app.formattedMobile, AppTheme.mobileColor),
        ],
      ),
    ],
  );
}

两个小标签分别显示WiFi和移动数据使用量。
不同颜色区分两种网络类型。
用户可以看到流量的来源构成。

小标签组件

小标签用于显示WiFi和移动数据的使用量:

Widget _buildMiniTag(String label, String value, Color color) {
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
    decoration: BoxDecoration(
      color: color.withOpacity(0.1),
      borderRadius: BorderRadius.circular(4.r),

padding很小因为这是辅助信息,不需要太突出。
背景色使用传入颜色的浅色版本。
小圆角4.r让标签看起来更精致。

    ),
    child: Text(
      '$label $value',
      style: TextStyle(fontSize: 10.sp, color: color),
    ),
  );
}

文字组合标签名和数值。
10.sp的小字号作为辅助信息。
文字颜色与背景色系一致。

使用量显示

右侧显示总使用量:

Widget _buildUsageInfo(AppUsage app) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.end,
    children: [
      Text(
        app.formattedTotal,

Column垂直排列数值和标签。
crossAxisAlignment设置右对齐,与左侧信息对称。
formattedTotal是格式化后的总流量字符串。

        style: TextStyle(
          fontSize: 16.sp,
          fontWeight: FontWeight.bold,
          color: AppTheme.primaryColor,
        ),
      ),

16.sp大字号突出显示,是整个item最醒目的元素。
bold加粗强调数据重要性。
主色调让数字更吸引眼球。

      SizedBox(height: 2.h),
      Text(
        '总计',
        style: TextStyle(fontSize: 11.sp, color: AppTheme.textSecondary),
      ),
    ],
  );
}

小间距2.h让数字和标签紧凑排列。
"总计"标签用11.sp小字号。
次要颜色起到注释作用但不抢眼。

Controller实现

控制器管理页面的状态和业务逻辑:

class DataRankingController extends GetxController {
  final rankingList = <AppUsage>[].obs;
  final selectedPeriod = 0.obs;
  final isLoading = false.obs;

三个核心响应式变量:排行数据、选中时间段、加载状态。
.obs将普通变量转换为响应式变量。
任一变量变化都会触发UI自动更新。

  
  void onInit() {
    super.onInit();
    loadRanking();
  }

  void changePeriod(int index) {
    if (selectedPeriod.value == index) return;

onInit在控制器初始化时调用,加载初始数据。
changePeriod处理时间段切换。
先判断是否重复点击,避免无意义的重复请求。

    selectedPeriod.value = index;
    loadRanking();
  }

  void loadRanking() async {
    isLoading.value = true;
    await Future.delayed(const Duration(milliseconds: 300));

更新选中状态后重新加载数据。
isLoading设为true显示加载动画。
模拟网络延迟,实际项目中是真实的API请求。

    final multiplier = selectedPeriod.value == 0 ? 1 : (selectedPeriod.value == 1 ? 7 : 30);
    
    rankingList.value = [
      AppUsage(
        id: '1',
        appName: '抖音',

multiplier根据时间段计算数据倍数。
今日倍数1,本周倍数7,本月倍数30。
构造模拟的应用使用数据。

        packageName: 'com.ss.android.ugc.aweme',
        totalUsage: 1024 * 1024 * 1100 * multiplier,
        wifiUsage: 800 * 1024 * 1024 * multiplier,
        mobileUsage: 300 * 1024 * 1024 * multiplier,
        lastUsed: DateTime.now(),
      ),

packageName是应用的包名,用于唯一标识。
totalUsage、wifiUsage、mobileUsage分别是总流量、WiFi流量、移动流量。
lastUsed记录最后使用时间。

    ];
    
    rankingList.sort((a, b) => b.totalUsage.compareTo(a.totalUsage));
    isLoading.value = false;
  }
}

sort方法按总使用量降序排列。
compareTo返回比较结果,b在前实现降序。
最后关闭加载状态,显示排行列表。

空状态处理

当没有数据时显示友好的提示:

Widget _buildEmptyState() {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.leaderboard, size: 80.sp, color: Colors.grey.shade300),

Center让内容在屏幕中央显示。
Column垂直排列图标和文字。
排行榜图标暗示页面功能。

        SizedBox(height: 16.h),
        Text(
          '暂无排行数据',
          style: TextStyle(fontSize: 16.sp, color: AppTheme.textSecondary),
        ),

间距16.h让图标和文字不会太挤。
主提示文字用16.sp字号。
次要颜色让提示不会太突兀。

        SizedBox(height: 8.h),
        Text(
          '使用应用后会自动统计流量',
          style: TextStyle(fontSize: 14.sp, color: AppTheme.textSecondary),
        ),
      ],
    ),
  );
}

副提示说明数据来源。
14.sp稍小的字号作为补充说明。
整体设计简洁友好,不会让用户感到困惑。

写在最后

流量排行页面帮助用户快速找出流量消耗最多的应用。通过醒目的排名徽章、清晰的流量数据、便捷的时间切换,用户可以轻松管理自己的流量使用。

可以继续优化的方向:

  • 添加流量变化趋势指示(上升/下降箭头)
  • 支持按WiFi或移动数据单独排行
  • 添加应用分类筛选
  • 支持设置流量预警阈值

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐