news 2026/4/16 10:44:25

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/15 21:10:02

B站视频下载神器深度解析:如何高效获取1080P高清资源

B站视频下载神器深度解析&#xff1a;如何高效获取1080P高清资源 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader &#x1f633; 项目地址: https://gitcode.com/gh_mirrors/bi…

作者头像 李华
网站建设 2026/4/16 10:43:42

高效语音处理新选择|基于科哥构建的SenseVoice Small镜像应用

高效语音处理新选择&#xff5c;基于科哥构建的SenseVoice Small镜像应用 1. 引言&#xff1a;语音识别与情感分析的融合趋势 随着人工智能技术在语音领域的持续演进&#xff0c;传统的语音识别&#xff08;ASR&#xff09;已从单纯的“语音转文字”逐步向多模态语义理解发展…

作者头像 李华
网站建设 2026/4/16 12:58:38

零基础入门:用Meta-Llama-3-8B-Instruct快速实现会议纪要自动化

零基础入门&#xff1a;用Meta-Llama-3-8B-Instruct快速实现会议纪要自动化 1. 引言&#xff1a;为什么需要自动化的会议纪要工具&#xff1f; 在现代企业协作中&#xff0c;会议是信息同步、决策推进和跨部门协调的核心场景。然而&#xff0c;会后整理会议纪要往往耗时且重复…

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

人脸检测模型选型指南:为什么DamoFD-0.5G是轻量级最佳选择

人脸检测模型选型指南&#xff1a;为什么DamoFD-0.5G是轻量级最佳选择 你是不是也遇到过这样的情况&#xff1a;公司要做一个带人脸识别功能的门禁系统&#xff0c;或者开发一款美颜App的人脸追踪模块&#xff0c;结果一上手就卡在“用哪个模型”这个问题上&#xff1f;精度高…

作者头像 李华
网站建设 2026/4/16 12:58:25

Qwen快速体验指南:5分钟免配置,1块钱玩转大模型

Qwen快速体验指南&#xff1a;5分钟免配置&#xff0c;1块钱玩转大模型 你是不是也遇到过这样的场景&#xff1f;创业团队要给投资人做一场智能客服的Demo展示&#xff0c;时间紧、任务重&#xff0c;可团队里全是前端开发&#xff0c;没人会搭CUDA环境&#xff0c;也不懂Linu…

作者头像 李华
网站建设 2026/4/16 13:03:38

FilePizza终极指南:浏览器直连文件传输的完整方案

FilePizza终极指南&#xff1a;浏览器直连文件传输的完整方案 【免费下载链接】filepizza :pizza: Peer-to-peer file transfers in your browser 项目地址: https://gitcode.com/GitHub_Trending/fi/filepizza 还在为传统文件传输的繁琐流程而困扰吗&#xff1f;每次都…

作者头像 李华