news 2026/4/23 18:43:58

Flutter for OpenHarmony移动数据使用监管助手App实战 - 流量排行实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony移动数据使用监管助手App实战 - 流量排行实现

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

功能设计

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

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

页面整体结构

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

classDataRankingViewextendsGetView<DataRankingController>{constDataRankingView({super.key});

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

@overrideWidgetbuild(BuildContextcontext){returnScaffold(backgroundColor:AppTheme.backgroundColor,appBar:AppBar(

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

title:constText('流量排行'),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(){finalperiods=['今日','本周','本月'];returnContainer(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){returnExpanded(

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

child:GestureDetector(onTap:()=>controller.changePeriod(index),child:Obx(()=>AnimatedContainer(duration:constDuration(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(){returnObx((){if(controller.isLoading.value){returnconstCenter(child:CircularProgressIndicator());}

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

if(controller.rankingList.isEmpty){return_buildEmptyState();}returnListView.builder(padding:EdgeInsets.symmetric(horizontal:16.w),

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

itemCount:controller.rankingList.length,itemBuilder:(context,index){finalapp=controller.rankingList[index];return_buildRankingItem(app,index);},);});}

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

排行项组件

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

Widget_buildRankingItem(AppUsageapp,int index){finalisTopThree=index<3;returnGestureDetector(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){finalisTopThree=index<3;finalcolor=_getRankColor(index);returnContainer(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){case0:returnconstColor(0xFFFFD700);// 金色case1:returnconstColor(0xFFC0C0C0);// 银色

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

case2:returnconstColor(0xFFCD7F32);// 铜色default:returnAppTheme.primaryColor;}}

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

IconData_getRankIcon(int index){switch(index){case0:returnIcons.emoji_events;// 奖杯case1:returnIcons.workspace_premium;// 奖牌

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

case2:returnIcons.military_tech;// 勋章default:returnIcons.circle;}}

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

应用图标和信息

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

Widget_buildAppIcon(AppUsageapp){returnContainer(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(AppUsageapp){returnColumn(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(Stringlabel,Stringvalue,Colorcolor){returnContainer(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(AppUsageapp){returnColumn(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实现

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

classDataRankingControllerextendsGetxController{finalrankingList=<AppUsage>[].obs;finalselectedPeriod=0.obs;finalisLoading=false.obs;

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

@overridevoidonInit(){super.onInit();loadRanking();}voidchangePeriod(int index){if(selectedPeriod.value==index)return;

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

selectedPeriod.value=index;loadRanking();}voidloadRanking()async{isLoading.value=true;awaitFuture.delayed(constDuration(milliseconds:300));

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

finalmultiplier=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(){returnCenter(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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:25:06

如何快速掌握EPOCH:等离子体模拟新手的完整指南

如何快速掌握EPOCH&#xff1a;等离子体模拟新手的完整指南 【免费下载链接】epoch Particle-in-cell code for plasma physics simulations 项目地址: https://gitcode.com/gh_mirrors/epoc/epoch EPOCH是一款强大的开源粒子-in-cell&#xff08;PIC&#xff09;代码&a…

作者头像 李华
网站建设 2026/4/23 15:32:36

Emotion2Vec+ Large实战:上传音频就能识别快乐还是悲伤

Emotion2Vec Large实战&#xff1a;上传音频就能识别快乐还是悲伤 1. 背景与应用场景 在人机交互、智能客服、心理健康监测和语音助手等场景中&#xff0c;理解用户的情绪状态正变得越来越重要。传统的语音识别&#xff08;ASR&#xff09;只能转录“说了什么”&#xff0c;而…

作者头像 李华
网站建设 2026/4/23 13:59:33

MySQL 索引原理与优化实战指南:从失效场景到联合索引设计

文章目录一、MySQL中索引一定有效吗&#xff1f;如何排查索引的效果&#xff1f;二、索引失效的场景三、什么是索引下推&#xff1f;四、建索引时的注意事项五、联合索引的设计原则六、联合索引和多个单例索引的区别一、MySQL中索引一定有效吗&#xff1f;如何排查索引的效果&a…

作者头像 李华
网站建设 2026/3/12 6:37:11

BAAI/bge-m3部署最佳实践:生产环境配置参数详解

BAAI/bge-m3部署最佳实践&#xff1a;生产环境配置参数详解 1. 引言 1.1 业务场景描述 在构建现代AI应用的过程中&#xff0c;语义理解能力已成为检索系统、推荐引擎和智能客服等核心模块的基础支撑。特别是在检索增强生成&#xff08;RAG&#xff09;架构中&#xff0c;高质…

作者头像 李华
网站建设 2026/4/15 3:52:46

电商客服录音分析新玩法,用SenseVoiceSmall自动打标签

电商客服录音分析新玩法&#xff0c;用SenseVoiceSmall自动打标签 1. 背景与痛点&#xff1a;传统客服质检的瓶颈 在电商平台中&#xff0c;客服对话是用户体验的关键触点。传统的客服质量监控依赖人工抽检&#xff0c;效率低、成本高&#xff0c;且难以覆盖全部通话记录。即…

作者头像 李华
网站建设 2026/4/23 15:30:33

从单图到批量抠图|CV-UNet大模型镜像落地全流程

从单图到批量抠图&#xff5c;CV-UNet大模型镜像落地全流程 1. 引言&#xff1a;智能抠图的技术演进与现实需求 图像抠图&#xff08;Image Matting&#xff09;作为计算机视觉中的经典任务&#xff0c;长期以来在电商、设计、影视后期等领域扮演着关键角色。传统方法依赖绿幕…

作者头像 李华