Flutter for OpenHarmony移动数据使用监管助手App实战 - 流量排行实现
流量排行页面帮助用户快速找出流量消耗最多的应用。通过醒目的排名徽章、清晰的流量数据、便捷的时间切换,用户可以轻松管理自己的流量使用。添加流量变化趋势指示(上升/下降箭头)支持按WiFi或移动数据单独排行添加应用分类筛选支持设置流量预警阈值欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net。
流量排行页面展示应用的流量使用排名,帮助用户快速找出"流量大户"。这个功能对于流量有限的用户特别有用,可以针对性地管理高耗流量的应用。
功能设计
流量排行页面的核心功能:
- 按流量使用量从高到低排列应用
- 支持切换时间范围(今日、本周、本月)
- 显示排名、应用名称、流量使用量
- 前三名用特殊样式突出显示
- 点击应用可以进入详情页
页面整体结构
首先定义页面类,继承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
更多推荐




所有评论(0)