Flutter for OpenHarmony移动数据使用监管助手App实战 - 网络状态实现
网络状态页面是流量监控App的核心功能入口,采用清晰的信息展示和便捷操作设计。页面分为顶部状态卡片和下方功能入口两大部分:卡片通过渐变背景区分WiFi(绿色)和移动数据(橙色),直观展示网络类型、名称、信号强度(5级评价)、IP地址和网速;功能入口提供WiFi详情、移动网络设置和网速测试快捷通道。采用Flutter实现,通过枚举确保类型安全,计算属性转换原始数据为友好显示,响应式设计适配不同设备。
网络状态页面是流量监控App的重要入口,用户可以在这里查看当前连接的网络类型、信号强度,以及快速跳转到WiFi详情、移动网络详情、网速测试等功能。这个页面要做到信息清晰、操作便捷。
功能规划
网络状态页面需要展示的信息包括:
- 当前网络类型(WiFi还是移动数据)
- 网络名称(WiFi的SSID或运营商名称)
- 信号强度
- IP地址
- 快捷入口(WiFi详情、移动网络、网速测试)
页面分为两个主要区域:顶部的状态卡片展示当前网络信息,下方是功能入口列表。这种布局让用户一眼就能看到最重要的信息,同时方便进入更详细的设置。
数据模型定义
先定义网络信息的数据模型:
enum NetworkType { wifi, mobile, none }
class NetworkInfo {
final NetworkType type;
final String name;
final int signalStrength;
final String ipAddress;
final double downloadSpeed;
final double uploadSpeed;
final bool isConnected;
// 定义NetworkType枚举,包含wifi、mobile、none三种网络类型。
// NetworkInfo类封装网络信息,包含类型、名称、信号强度等属性。
// 所有属性都是final的,保证数据不可变性。
NetworkInfo({
this.type = NetworkType.none,
this.name = '',
this.signalStrength = 0,
this.ipAddress = '',
this.downloadSpeed = 0,
this.uploadSpeed = 0,
this.isConnected = false,
});
String get typeString {
switch (type) {
case NetworkType.wifi:
return 'WiFi';
case NetworkType.mobile:
return '移动数据';
// 构造函数使用命名参数,所有参数都有默认值。
// typeString是计算属性,根据type枚举返回对应的中文字符串。
// switch语句覆盖所有枚举值,确保类型安全。
case NetworkType.none:
return '未连接';
}
}
String get signalLevel {
if (signalStrength >= 80) return '极好';
if (signalStrength >= 60) return '良好';
if (signalStrength >= 40) return '一般';
if (signalStrength >= 20) return '较弱';
return '很弱';
}
}
枚举的使用:用NetworkType枚举表示网络类型,比用字符串或数字更安全,IDE也能提供更好的代码补全。
计算属性:typeString和signalLevel是计算属性,根据原始数据返回用户友好的文字。这样View层不需要做这些转换逻辑。
页面整体结构
class NetworkStatusView extends GetView<NetworkStatusController> {
const NetworkStatusView({super.key});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.backgroundColor,
appBar: AppBar(title: const Text('网络状态')),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildStatusCard(),
// NetworkStatusView继承GetView,自动获得controller属性。
// Scaffold设置背景色,AppBar标题为"网络状态"。
// body使用SingleChildScrollView包裹,确保内容可滚动。
SizedBox(height: 16.h),
_buildNetworkOptions(),
],
),
),
);
}
}
页面结构很简单,一个状态卡片加一个选项列表。用SingleChildScrollView包裹是为了在小屏设备上也能正常滚动。
网络状态卡片
状态卡片是页面的视觉焦点,用渐变背景让它更醒目:
Widget _buildStatusCard() {
return Obx(() {
final network = controller.networkInfo.value;
final isWifi = network.type == NetworkType.wifi;
return Container(
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
// _buildStatusCard用Obx包裹,networkInfo变化时自动重建。
// 根据网络类型判断是否为WiFi,用于后续颜色选择。
// Container设置24像素内边距,使用渐变背景装饰。
colors: isWifi
? [AppTheme.wifiColor, AppTheme.wifiColor.withOpacity(0.7)]
: [AppTheme.mobileColor, AppTheme.mobileColor.withOpacity(0.7)],
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: (isWifi ? AppTheme.wifiColor : AppTheme.mobileColor).withOpacity(0.3),
blurRadius: 15.r,
offset: Offset(0, 8.h),
),
],
),
child: Column(
children: [
// WiFi使用绿色系渐变,移动数据使用橙色系渐变。
// 20像素圆角让卡片看起来更柔和。
// 阴影颜色与卡片主色调一致,透明度30%。
_buildNetworkIcon(network),
SizedBox(height: 16.h),
_buildNetworkName(network),
SizedBox(height: 8.h),
_buildSignalInfo(network),
SizedBox(height: 8.h),
_buildIpAddress(network),
SizedBox(height: 20.h),
_buildSpeedInfo(network),
],
),
);
});
}
颜色区分网络类型:WiFi用绿色系,移动数据用橙色系。这种颜色编码让用户不用看文字就能快速分辨当前网络类型。
阴影颜色的处理:阴影颜色用对应网络类型的主色调,而不是通用的黑色。这样阴影会带有一点颜色,和卡片更协调,视觉效果更好。
渐变方向:从左上到右下的渐变,模拟光源从左上方照射的效果,让卡片更有立体感。
网络图标组件
Widget _buildNetworkIcon(NetworkInfo network) {
return Container(
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
network.type == NetworkType.wifi
? Icons.wifi
: Icons.signal_cellular_alt,
// 图标容器设置80x80像素大小,圆形形状。
// 背景使用20%透明度的白色,在渐变背景上形成半透明效果。
// 根据网络类型选择WiFi图标或信号图标。
size: 48.sp,
color: Colors.white,
),
);
}
图标放在一个半透明的圆形容器里,既突出又不会太抢眼。图标大小48sp,在卡片中足够醒目。
网络名称和信号信息
Widget _buildNetworkName(NetworkInfo network) {
return Text(
network.name.isNotEmpty ? network.name : '未连接',
style: TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
);
}
Widget _buildSignalInfo(NetworkInfo network) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
// _buildNetworkName显示网络名称,如果为空则显示"未连接"。
// 使用22sp大字号和粗体,白色文字在渐变背景上清晰可见。
// _buildSignalInfo使用Row横向排列信号图标和文字。
children: [
Icon(Icons.signal_cellular_alt, size: 16.sp, color: Colors.white70),
SizedBox(width: 4.w),
Text(
'信号${network.signalLevel} · ${network.signalStrength}%',
style: TextStyle(fontSize: 14.sp, color: Colors.white70),
),
],
);
}
Widget _buildIpAddress(NetworkInfo network) {
return Text(
'IP: ${network.ipAddress}',
style: TextStyle(fontSize: 12.sp, color: Colors.white60),
// 信号图标使用16sp大小,70%透明度的白色。
// 信号文字同时显示等级描述和百分比数值。
// IP地址使用12sp小字号,60%透明度形成层次感。
);
}
信息层次:网络名称最大最醒目,信号信息次之,IP地址最小。这种层次让用户能快速抓住重点。
信号显示:同时显示文字描述(极好/良好/一般)和百分比数值,满足不同用户的阅读习惯。
实时网速显示
Widget _buildSpeedInfo(NetworkInfo network) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildSpeedItem(Icons.arrow_downward, '下载', network.downloadSpeed),
SizedBox(width: 24.w),
// 网速信息容器使用15%透明度的白色背景,12像素圆角。
// 水平内边距20像素,垂直内边距12像素。
// Row使用mainAxisSize.min让容器宽度自适应内容。
Container(width: 1, height: 30.h, color: Colors.white30),
SizedBox(width: 24.w),
_buildSpeedItem(Icons.arrow_upward, '上传', network.uploadSpeed),
],
),
);
}
Widget _buildSpeedItem(IconData icon, String label, double speed) {
return Column(
children: [
Row(
children: [
Icon(icon, size: 14.sp, color: Colors.white70),
SizedBox(width: 4.w),
Text(label, style: TextStyle(fontSize: 12.sp, color: Colors.white70)),
// 下载和上传之间用30像素高的白色分隔线隔开。
// _buildSpeedItem构建单个速度项,接收图标、标签和速度值。
// 顶部Row包含方向箭头图标和标签文字。
],
),
SizedBox(height: 4.h),
Text(
'${speed.toStringAsFixed(1)} MB/s',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.white),
),
],
);
}
网速信息放在一个半透明的容器里,和上面的信息区分开。下载和上传用箭头图标区分,直观易懂。
功能入口列表
Widget _buildNetworkOptions() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10.r,
offset: Offset(0, 4.h),
),
],
),
child: Column(
// 功能入口列表容器使用白色背景,16像素圆角。
// 添加轻微阴影效果,透明度3%,模糊半径10像素。
// child使用Column垂直排列多个选项。
children: [
_buildOptionTile(
'WiFi详情',
'Home_WiFi_5G',
Icons.wifi,
AppTheme.wifiColor,
() => Get.toNamed(Routes.WIFI_DETAIL),
),
Divider(height: 1, indent: 72.w),
_buildOptionTile(
'移动网络',
'中国移动 5G',
Icons.signal_cellular_alt,
AppTheme.mobileColor,
() => Get.toNamed(Routes.MOBILE_DETAIL),
// 第一个选项是WiFi详情,显示当前WiFi名称。
// Divider分隔线indent设为72像素,从图标右边开始。
// 第二个选项是移动网络,显示运营商信息。
),
Divider(height: 1, indent: 72.w),
_buildOptionTile(
'网络测速',
'测试当前网络速度',
Icons.speed,
AppTheme.primaryColor,
() => Get.toNamed(Routes.SPEED_TEST),
),
],
),
);
}
Divider的indent:分隔线从图标右边开始,不是从最左边。这样视觉上更整齐,也是iOS风格的常见做法。indent值72.w是图标容器宽度加左边距。
选项项组件
Widget _buildOptionTile(
String title,
String subtitle,
IconData icon,
Color color,
VoidCallback onTap,
) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
child: Row(
children: [
// _buildOptionTile是复用组件,接收标题、副标题、图标、颜色和点击回调。
// InkWell提供水波纹点击效果,borderRadius与容器圆角一致。
// Padding设置水平16像素、垂直14像素的内边距。
Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(icon, color: color, size: 24.sp),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图标容器44x44像素,背景使用对应颜色的10%透明度。
// 12像素圆角让图标容器看起来更柔和。
// Expanded包裹文字区域,占据剩余空间。
Text(
title,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
color: AppTheme.textPrimary,
),
),
SizedBox(height: 2.h),
Text(
subtitle,
style: TextStyle(fontSize: 13.sp, color: AppTheme.textSecondary),
),
],
),
),
Icon(Icons.chevron_right, color: AppTheme.textSecondary, size: 20.sp),
// 标题使用15sp字号和500字重,主要文字颜色。
// 副标题使用13sp字号,次要文字颜色,形成层次。
// 右侧箭头图标提示用户可以点击进入下一级页面。
],
),
),
);
}
InkWell的使用:用InkWell而不是GestureDetector,因为InkWell有水波纹点击效果,反馈更明显。borderRadius要和容器的圆角一致,否则水波纹会溢出。
图标容器的颜色:每个选项的图标容器用对应功能的主色调的10%透明度,既有区分度又不会太抢眼。
右侧箭头:chevron_right图标是视觉暗示,告诉用户这个选项可以点击进入下一级页面。
Controller实现
class NetworkStatusController extends GetxController {
final networkInfo = Rx<NetworkInfo>(NetworkInfo());
Timer? _refreshTimer;
void onInit() {
super.onInit();
loadNetworkInfo();
startAutoRefresh();
}
void onClose() {
_refreshTimer?.cancel();
super.onClose();
}
// NetworkStatusController管理网络状态页面的数据。
// networkInfo使用Rx包装NetworkInfo对象,整体变化时触发更新。
// onInit中加载网络信息并启动自动刷新。
void loadNetworkInfo() {
// 实际项目中这里调用系统API获取网络信息
networkInfo.value = NetworkInfo(
type: NetworkType.wifi,
name: 'Home_WiFi_5G',
signalStrength: 85,
ipAddress: '192.168.1.100',
downloadSpeed: 125.6,
uploadSpeed: 45.2,
isConnected: true,
);
}
void startAutoRefresh() {
_refreshTimer = Timer.periodic(const Duration(seconds: 5), (_) {
loadNetworkInfo();
// loadNetworkInfo方法加载网络信息,这里使用模拟数据。
// 实际项目中需要调用系统API获取真实的网络状态。
// startAutoRefresh每5秒刷新一次网络信息。
});
}
}
定时刷新:网络状态可能随时变化,用Timer每5秒刷新一次。在onClose中取消Timer,避免内存泄漏。
Rx包装:networkInfo用Rx<NetworkInfo>包装,整个对象变化时UI会自动更新。如果只是某个属性变化,需要调用networkInfo.refresh()或重新赋值。
网络状态监听
实际项目中,除了定时刷新,还应该监听系统的网络状态变化事件:
void setupNetworkListener() {
// 使用connectivity_plus包监听网络变化
Connectivity().onConnectivityChanged.listen((result) {
if (result == ConnectivityResult.wifi) {
networkInfo.update((val) {
val?.type = NetworkType.wifi;
});
} else if (result == ConnectivityResult.mobile) {
networkInfo.update((val) {
val?.type = NetworkType.mobile;
});
// setupNetworkListener方法设置网络状态监听器。
// 使用connectivity_plus包的onConnectivityChanged流监听网络变化。
// 根据ConnectivityResult更新networkInfo的type属性。
} else {
networkInfo.update((val) {
val?.type = NetworkType.none;
});
}
loadNetworkInfo(); // 重新加载详细信息
});
}
这样网络切换时能立即更新UI,不用等定时器。
无网络状态处理
当设备没有网络连接时,需要显示不同的UI:
Widget _buildStatusCard() {
return Obx(() {
final network = controller.networkInfo.value;
if (!network.isConnected) {
return _buildNoNetworkCard();
}
// 正常的网络状态卡片...
});
}
Widget _buildNoNetworkCard() {
return Container(
padding: EdgeInsets.all(24.w),
// _buildStatusCard首先检查网络连接状态。
// 如果未连接,返回无网络状态卡片。
// _buildNoNetworkCard构建无网络时的提示界面。
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(20.r),
),
child: Column(
children: [
Icon(Icons.signal_wifi_off, size: 60.sp, color: Colors.grey),
SizedBox(height: 16.h),
Text('未连接网络', style: TextStyle(fontSize: 18.sp, color: Colors.grey.shade600)),
SizedBox(height: 8.h),
Text('请检查WiFi或移动数据设置', style: TextStyle(fontSize: 14.sp, color: Colors.grey)),
SizedBox(height: 20.h),
// 无网络卡片使用灰色背景,与有网络时的彩色形成对比。
// 显示断开连接的WiFi图标,60sp大小。
// 提示文字告知用户当前状态和建议操作。
OutlinedButton(
onPressed: () => controller.loadNetworkInfo(),
child: Text('重新检测'),
),
],
),
);
}
无网络时用灰色调,和有网络时的彩色形成对比。提供"重新检测"按钮,让用户可以手动刷新。
路由配置
GetPage(
name: Routes.NETWORK_STATUS,
page: () => const NetworkStatusView(),
binding: NetworkStatusBinding(),
),
写在最后
网络状态页面是用户了解当前网络情况的窗口,信息要准确、更新要及时、操作要便捷。通过颜色区分网络类型、实时显示网速、提供快捷入口,可以让用户快速获取想要的信息。
后续可以考虑的优化:
- 添加网络历史记录,查看过去连接过的网络
- 网络质量评分,综合信号、延迟、丢包率给出评分
- 网络诊断功能,帮助用户排查网络问题
- 支持快速切换WiFi和移动数据
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)