网络状态页面是流量监控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也能提供更好的代码补全。

计算属性typeStringsignalLevel是计算属性,根据原始数据返回用户友好的文字。这样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包装networkInfoRx<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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐