Flutter for OpenHarmony移动数据使用监管助手App实战 - 应用列表实现
本文介绍了流量监控App中应用列表页面的设计与实现。该页面通过饼图直观展示各应用流量占比,并提供排序、筛选功能。顶部统计区采用饼图和总流量数字的对比布局,中间排序栏提供总流量、WiFi、移动数据三种排序方式。实现细节包括:使用fl_chart库绘制饼图、响应式状态管理、胶囊式排序按钮、系统应用开关等,整体设计注重数据可视化与交互体验。
应用列表页面展示设备上各个应用的流量使用情况,帮助用户找出"流量大户"。这个页面涉及到饼图展示、列表排序、筛选功能等,是流量监控App中交互比较丰富的一个模块。
页面布局规划
应用列表页面分为三个区域:
- 顶部统计区:饼图展示流量占比,总流量数字,系统应用开关
- 排序选项栏:按总流量、WiFi、移动数据排序
- 应用列表:展示每个应用的详细流量信息
顶部统计区实现
用饼图直观展示各应用的流量占比:
Widget _buildHeader() {
return Container(
margin: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
Container作为统计区的容器。
margin和padding设置外边距和内边距。
白色背景与页面灰色背景对比。
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
children: [
Expanded(
child: Obx(() => SizedBox(
16.r圆角保持视觉一致。
Row横向排列饼图和统计信息。
Expanded让饼图占据一半空间。
height: 120.h,
child: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: 30.r,
sections: _buildPieSections(),
固定高度120.h的饼图容器。
PieChart是fl_chart库的饼图组件。
sectionsSpace设置扇区之间的间隙。
),
),
)),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
centerSpaceRadius设置中心空白半径。
间距16.w让饼图和统计信息不挤。
右侧Column显示统计信息。
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('应用总流量', style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary)),
SizedBox(height: 4.h),
Obx(() => Text(
crossAxisAlignment让内容左对齐。
标签"应用总流量"用12.sp小字号。
Obx监听totalUsage实现响应式更新。
controller.formatBytes(controller.totalUsage),
style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold, color: AppTheme.textPrimary),
)),
SizedBox(height: 12.h),
_buildSystemAppToggle(),
formatBytes格式化字节数为可读字符串。
20.sp大字号突出显示总流量。
_buildSystemAppToggle构建系统应用开关。
],
),
),
],
),
);
}
闭合Column、Expanded、Row、Container。
左边饼图右边统计信息的布局。
整体设计直观清晰。
饼图扇区生成
生成饼图的各个扇区:
List<PieChartSectionData> _buildPieSections() {
final colors = [
AppTheme.primaryColor,
AppTheme.secondaryColor,
AppTheme.wifiColor,
AppTheme.mobileColor,
返回PieChartSectionData列表。
colors数组定义扇区颜色。
使用App主题色保持视觉一致。
Colors.purple,
Colors.pink,
Colors.teal,
];
final apps = controller.filteredList.take(5).toList();
final total = controller.totalUsage;
紫色、粉色、青色作为补充颜色。
只取前5个应用显示在饼图上。
total是所有应用的总流量。
return List.generate(apps.length, (index) {
final percentage = total > 0 ? (apps[index].totalUsage / total) * 100 : 0;
return PieChartSectionData(
color: colors[index % colors.length],
List.generate生成扇区数据。
计算每个应用的流量占比。
颜色循环使用确保不重复。
value: percentage.toDouble(),
title: '${percentage.toStringAsFixed(0)}%',
radius: 35.r,
titleStyle: TextStyle(fontSize: 10.sp, fontWeight: FontWeight.bold, color: Colors.white),
);
});
}
value是扇区的数值。
title显示百分比文字。
radius设置扇区半径。
系统应用开关
控制是否显示系统应用:
Widget _buildSystemAppToggle() {
return Row(
children: [
Obx(() => Checkbox(
value: controller.showSystemApps.value,
onChanged: (_) => controller.toggleSystemApps(),
Row横向排列复选框和文字。
Obx监听showSystemApps状态。
onChanged切换显示状态。
activeColor: AppTheme.primaryColor,
)),
Text('显示系统应用', style: TextStyle(fontSize: 12.sp)),
],
);
}
主色调作为复选框激活颜色。
12.sp小字号适合辅助选项。
默认隐藏系统应用。
排序选项栏
三个排序选项用胶囊按钮展示:
Widget _buildSortOptions() {
final options = ['总流量', 'WiFi', '移动数据'];
return Container(
height: 40.h,
margin: EdgeInsets.symmetric(horizontal: 16.w),
options数组定义排序选项。
Container作为选项栏的容器。
固定高度40.h。
child: Row(
children: List.generate(options.length, (index) {
return Padding(
padding: EdgeInsets.only(right: 8.w),
child: GestureDetector(
Row横向排列选项按钮。
List.generate生成选项按钮。
Padding添加右侧间距。
onTap: () => controller.changeSortType(index),
child: Obx(() => Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
GestureDetector处理点击事件。
Obx监听sortType实现响应式更新。
padding设置按钮内边距。
color: controller.sortType.value == index
? AppTheme.primaryColor
: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
选中时用主色背景。
未选中时用白色背景。
20.r圆角形成胶囊形状。
child: Text(
options[index],
style: TextStyle(
fontSize: 12.sp,
color: controller.sortType.value == index
? Colors.white
: AppTheme.textSecondary,
),
),
)),
),
);
}),
),
);
}
选中时白色文字,未选中时灰色文字。
12.sp字号适合按钮文字。
整体设计简洁直观。
应用列表实现
展示所有应用的流量信息:
Widget _buildAppList() {
return Obx(() => ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: controller.filteredList.length,
itemBuilder: (context, index) {
Obx监听filteredList实现响应式更新。
ListView.builder懒加载列表项。
padding设置列表内边距。
final app = controller.filteredList[index];
return GestureDetector(
onTap: () => Get.toNamed(Routes.APP_DETAIL, arguments: app),
child: Container(
获取当前索引的应用数据。
GestureDetector处理点击跳转详情。
arguments传递应用数据。
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
底部margin让列表项之间有间距。
白色背景与页面灰色背景对比。
12.r圆角保持视觉一致。
child: Row(
children: [
_buildAppIcon(app),
SizedBox(width: 12.w),
Expanded(child: _buildAppInfo(app)),
Row横向排列图标、信息、流量。
_buildAppIcon构建应用图标。
Expanded让信息区域占据剩余空间。
_buildUsageInfo(app),
SizedBox(width: 8.w),
Icon(Icons.chevron_right, color: AppTheme.textSecondary, size: 20.sp),
],
),
),
);
},
));
}
_buildUsageInfo构建流量信息。
右箭头表示可点击查看详情。
闭合ListView完成列表构建。
应用图标
根据应用类型显示不同图标:
Widget _buildAppIcon(AppUsage app) {
return Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
Container作为图标容器。
44.w的尺寸适合列表项。
浅色背景让图标区域柔和。
borderRadius: BorderRadius.circular(10.r),
),
child: Icon(
app.isSystemApp ? Icons.android : Icons.apps,
color: AppTheme.primaryColor,
size: 24.sp,
),
);
}
系统应用用android图标。
普通应用用apps图标。
主色调图标与App风格一致。
应用信息区域
显示应用名称和分项流量:
Widget _buildAppInfo(AppUsage app) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(app.appName, style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
Column垂直排列名称和流量。
crossAxisAlignment让内容左对齐。
应用名称用14.sp字号,w600加粗。
if (app.isSystemApp) ...[
SizedBox(width: 6.w),
Container(
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
decoration: BoxDecoration(
系统应用显示"系统"标签。
展开运算符…条件添加组件。
小内边距让标签紧凑。
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(4.r),
),
child: Text('系统', style: TextStyle(fontSize: 10.sp, color: AppTheme.textSecondary)),
),
],
],
),
灰色背景区分系统应用。
4.r小圆角让标签更精致。
10.sp小字号适合标签。
SizedBox(height: 4.h),
Row(
children: [
Icon(Icons.wifi, size: 12.sp, color: AppTheme.wifiColor),
SizedBox(width: 4.w),
间距4.h后显示分项流量。
Row横向排列WiFi和移动数据。
WiFi图标用绿色。
Text(app.formattedWifi, style: TextStyle(fontSize: 11.sp, color: AppTheme.textSecondary)),
SizedBox(width: 12.w),
Icon(Icons.signal_cellular_alt, size: 12.sp, color: AppTheme.mobileColor),
显示WiFi流量数值。
间距12.w分隔WiFi和移动数据。
移动数据图标用橙色。
SizedBox(width: 4.w),
Text(app.formattedMobile, style: TextStyle(fontSize: 11.sp, color: AppTheme.textSecondary)),
],
),
],
);
}
显示移动数据流量数值。
11.sp小字号作为辅助信息。
整体设计信息层次分明。
流量信息区域
显示总流量和占比:
Widget _buildUsageInfo(AppUsage app) {
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
app.formattedTotal,
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold, color: AppTheme.primaryColor),
),
Column垂直排列总流量和占比。
crossAxisAlignment让内容右对齐。
总流量用14.sp加粗,主色调。
SizedBox(height: 4.h),
Text(
'${((app.totalUsage / controller.totalUsage) * 100).toStringAsFixed(1)}%',
style: TextStyle(fontSize: 11.sp, color: AppTheme.textSecondary),
),
],
);
}
间距4.h后显示占比百分比。
计算该应用占总流量的百分比。
11.sp小字号作为辅助信息。
Controller层实现
控制器管理应用列表的状态和逻辑:
class AppsController extends GetxController {
final isLoading = false.obs;
final sortType = 0.obs;
final showSystemApps = false.obs;
final appList = <AppUsage>[].obs;
final filteredList = <AppUsage>[].obs;
isLoading控制加载状态。
sortType记录当前排序类型。
showSystemApps控制是否显示系统应用。
void changeSortType(int type) {
sortType.value = type;
filterAndSort();
}
void toggleSystemApps() {
showSystemApps.value = !showSystemApps.value;
filterAndSort();
}
changeSortType更新排序类型后重新筛选排序。
toggleSystemApps切换系统应用显示状态。
两个方法都调用filterAndSort更新列表。
void filterAndSort() {
var list = appList.where((app) {
if (!showSystemApps.value && app.isSystemApp) return false;
return true;
}).toList();
filterAndSort执行筛选和排序。
where过滤掉系统应用(如果开关关闭)。
toList转换为列表。
switch (sortType.value) {
case 0:
list.sort((a, b) => b.totalUsage.compareTo(a.totalUsage));
break;
case 1:
list.sort((a, b) => b.wifiUsage.compareTo(a.wifiUsage));
case 0按总流量排序。
case 1按WiFi流量排序。
b.compareTo(a)实现降序排列。
break;
case 2:
list.sort((a, b) => b.mobileUsage.compareTo(a.mobileUsage));
break;
}
filteredList.value = list;
}
case 2按移动数据排序。
更新filteredList触发UI更新。
流量大的排在前面。
int get totalUsage => filteredList.fold(0, (sum, app) => sum + app.totalUsage);
}
totalUsage计算所有应用的总流量。
fold方法累加列表中的值。
getter确保每次访问都是最新值。
写在最后
应用列表页面的交互比较丰富,但核心就是筛选和排序。把这两个逻辑封装好,UI层就很简单了。饼图直观展示流量占比,列表详细展示每个应用的信息。
可以继续优化的方向:
- 添加搜索功能
- 支持多选批量操作
- 添加流量使用趋势图
- 支持按时间段筛选
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)