SIM卡管理页面让用户查看和管理双卡设备上的SIM卡信息,包括运营商、流量使用情况、套餐余量等。对于使用双卡的用户,这个功能可以帮助他们更好地分配流量使用。

功能设计

SIM卡管理页面需要实现:

  • 显示每张SIM卡的基本信息
  • 显示每张卡的流量使用情况
  • 支持启用/禁用SIM卡数据
  • 设置默认数据卡

页面整体结构

class SimManagerView extends GetView<SimManagerController> {
  const SimManagerView({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppTheme.backgroundColor,
      appBar: AppBar(

上面这段代码定义了SIM卡管理页面的基础结构。SimManagerView继承自GetView,这样可以直接通过controller属性访问控制器。
Scaffold是Flutter的基础页面脚手架,提供了AppBar、body等标准布局区域。backgroundColor设置页面背景色为主题定义的背景色。

        title: const Text('SIM卡管理'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () => controller.refreshSimInfo(),
          ),
        ],
      ),

AppBar的title设置页面标题为"SIM卡管理"。actions数组中添加了一个刷新按钮,点击时调用controller的refreshSimInfo方法。
这个刷新按钮让用户可以手动更新SIM卡信息,比如插拔卡后需要重新获取数据。IconButton是Material Design风格的图标按钮。

      body: SingleChildScrollView(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            _buildSimCard(0),
            SizedBox(height: 16.h),
            _buildSimCard(1),

body部分使用SingleChildScrollView包裹,确保内容超出屏幕时可以滚动。padding设置16.w的内边距,.w是flutter_screenutil的屏幕适配单位。
Column垂直排列子组件,_buildSimCard(0)和_buildSimCard(1)分别构建两张SIM卡的信息卡片,参数是卡槽索引。SizedBox添加16.h的垂直间距。

            SizedBox(height: 16.h),
            _buildDefaultDataCard(),
            SizedBox(height: 16.h),
            _buildTotalUsage(),
          ],
        ),
      ),
    );
  }
}

继续添加默认数据卡设置区域和双卡总流量统计区域。_buildDefaultDataCard()构建默认数据卡选择组件,_buildTotalUsage()构建流量汇总展示。
每个组件之间用SizedBox添加16.h的间距,保持视觉上的统一和整洁。整个页面从上到下依次展示:SIM1卡片、SIM2卡片、默认数据卡设置、总流量统计。

SIM卡信息卡片

SIM卡信息卡片是页面的核心组件,需要根据卡槽是否有卡、卡是否启用来显示不同的内容:

Widget _buildSimCard(int index) {
  return Obx(() {
    if (index >= controller.simCards.length) {
      return _buildEmptySimSlot(index);
    }
    
    final sim = controller.simCards[index];
    final isActive = sim['active'] as bool;

_buildSimCard方法接收卡槽索引参数,返回对应的SIM卡信息卡片。Obx是GetX的响应式包装器,当simCards数据变化时自动重建UI。
首先判断索引是否超出simCards列表长度,如果超出说明该卡槽没有SIM卡,调用_buildEmptySimSlot显示空卡槽状态。
从simCards列表中获取对应索引的SIM卡数据,使用as进行类型转换获取active状态。实际项目中建议定义Model类来保证类型安全。

    final carrier = sim['carrier'] as String;
    final usage = sim['usage'] as String;
    final remaining = sim['remaining'] as String;
    final phoneNumber = sim['phoneNumber'] as String;
    
    return Container(
      decoration: BoxDecoration(

继续从Map中解构出运营商名称、使用量、剩余量、手机号等字段。这些数据将在卡片中展示给用户。
Container是Flutter中最常用的布局容器,可以设置装饰、内边距、尺寸等属性。BoxDecoration用于定义容器的视觉样式。

        color: Colors.white,
        borderRadius: BorderRadius.circular(16.r),
        border: isActive
            ? Border.all(color: AppTheme.primaryColor, width: 2)
            : null,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.03),

设置卡片背景为白色,圆角为16.r(屏幕适配单位)。border属性根据isActive状态决定是否显示边框,启用的卡有2像素宽的主色调边框。
boxShadow添加阴影效果,使用3%透明度的黑色,让卡片看起来有轻微的悬浮感。这种设计让启用和禁用的卡片在视觉上有明显区分。

            blurRadius: 8.r,
            offset: Offset(0, 2.h),
          ),
        ],
      ),
      child: Column(
        children: [
          _buildSimHeader(index, sim),

blurRadius设置阴影模糊半径为8.r,offset设置阴影向下偏移2.h像素。这个参数组合形成自然的投影效果。
Column垂直排列卡片内容,首先调用_buildSimHeader构建卡片头部,传入卡槽索引和SIM卡数据。头部包含SIM卡图标、运营商信息和启用开关。

          if (isActive) ...[
            Divider(height: 1),
            _buildSimUsage(sim),
            Divider(height: 1),
            _buildSimActions(index, sim),
          ],
        ],
      ),
    );
  });
}

使用if语句配合展开运算符…[]实现条件渲染,只有当SIM卡启用时才显示流量使用区域和操作按钮区域。
Divider是分隔线组件,height: 1表示分隔线占用1像素高度。_buildSimUsage显示流量使用情况,_buildSimActions显示操作按钮。
这种设计让禁用的卡片更简洁,只显示基本信息,启用的卡片则展示完整功能。

卡片头部实现

卡片头部展示SIM卡的基本信息和启用开关:

Widget _buildSimHeader(int index, Map<String, dynamic> sim) {
  final isActive = sim['active'] as bool;
  final carrier = sim['carrier'] as String;
  final phoneNumber = sim['phoneNumber'] as String;
  
  return Padding(
    padding: EdgeInsets.all(16.w),
    child: Row(

_buildSimHeader方法接收卡槽索引和SIM卡数据Map。首先解构出需要显示的字段:启用状态、运营商名称、手机号。
Padding为内容添加16.w的内边距,Row用于横向排列子组件。头部布局是:左侧图标区域、中间文字信息、右侧开关。

      children: [
        Container(
          width: 56.w,
          height: 56.w,
          decoration: BoxDecoration(
            color: isActive
                ? AppTheme.primaryColor.withOpacity(0.1)
                : Colors.grey.shade100,

Container创建一个56x56的正方形图标容器。decoration设置背景色,启用状态用主色调的10%透明度,禁用状态用浅灰色。
这种颜色设计让用户一眼就能分辨SIM卡的启用状态,启用时有蓝色调,禁用时是灰色调。

            borderRadius: BorderRadius.circular(14.r),
          ),
          child: Stack(
            alignment: Alignment.center,
            children: [
              Icon(
                Icons.sim_card,
                color: isActive ? AppTheme.primaryColor : Colors.grey,

圆角设置为14.r,和56的宽度形成约1:4的比例,看起来比较柔和。Stack允许子组件叠加显示,alignment设置子组件居中对齐。
Icon显示SIM卡图标,颜色根据启用状态变化。启用时用主色调,禁用时用灰色,和背景色形成呼应。

                size: 32.sp,
              ),
              Positioned(
                bottom: 4.h,
                right: 4.w,
                child: Container(
                  width: 18.w,
                  height: 18.w,

图标大小设置为32.sp。Positioned用于在Stack中精确定位子组件,这里将卡槽编号小圆圈定位在右下角。
小圆圈容器大小为18x18,用于显示卡槽编号(1或2),让用户知道这是哪个卡槽的SIM卡。

                  decoration: BoxDecoration(
                    color: isActive ? AppTheme.wifiColor : Colors.grey,
                    shape: BoxShape.circle,
                    border: Border.all(color: Colors.white, width: 2),
                  ),
                  child: Center(
                    child: Text(
                      '${index + 1}',

小圆圈使用BoxShape.circle设置为圆形,启用时绿色背景,禁用时灰色背景。白色边框2像素宽,让小圆圈和底层图标有清晰的分隔。
Center将文字居中,显示卡槽编号。index从0开始,所以显示时加1,变成1和2。

                      style: TextStyle(
                        fontSize: 10.sp,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),

编号文字使用10sp的小字号,粗体,白色,在彩色背景上清晰可见。这种设计在有限空间内传达了卡槽信息。
整个图标区域的设计:SIM卡图标居中,右下角叠加一个带编号的小圆圈,颜色根据状态变化。

        SizedBox(width: 16.w),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Text(

SizedBox添加16.w的水平间距。Expanded让中间的文字区域占据剩余空间。Column垂直排列文字信息,crossAxisAlignment设置左对齐。
内层Row用于横向排列SIM名称和状态标签。

                    'SIM ${index + 1}',
                    style: TextStyle(
                      fontSize: 18.sp,
                      fontWeight: FontWeight.bold,
                      color: AppTheme.textPrimary,
                    ),
                  ),
                  if (isActive) ...[

显示"SIM 1"或"SIM 2",字号18sp,粗体,使用主要文字颜色。这是卡片的主标题,视觉上最突出。
if (isActive)配合展开运算符,只有启用状态才显示后面的状态标签。

                    SizedBox(width: 8.w),
                    Container(
                      padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
                      decoration: BoxDecoration(
                        color: AppTheme.wifiColor.withOpacity(0.1),
                        borderRadius: BorderRadius.circular(8.r),
                      ),

状态标签和SIM名称之间有8.w的间距。Container创建标签容器,水平内边距8.w,垂直内边距2.h。
背景色使用绿色的10%透明度,圆角8.r,形成一个小药丸形状的标签。

                      child: Text(
                        '已启用',
                        style: TextStyle(
                          fontSize: 10.sp,
                          color: AppTheme.wifiColor,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                  ],
                ],
              ),

标签文字"已启用",字号10sp,绿色,中等粗细。这个小标签让用户快速确认SIM卡的启用状态。
整个Row的效果是:SIM名称在左,启用状态标签紧跟其后(仅启用时显示)。

              SizedBox(height: 4.h),
              Text(
                carrier,
                style: TextStyle(fontSize: 14.sp, color: AppTheme.textSecondary),
              ),
              SizedBox(height: 2.h),
              Text(
                phoneNumber,
                style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary),
              ),

SIM名称下方显示运营商名称,字号14sp,次要文字颜色。再下方显示手机号,字号12sp,同样是次要颜色。
三行文字形成层次:SIM名称18sp最大,运营商14sp中等,手机号12sp最小。这种递减的字号设计突出主要信息。

            ],
          ),
        ),
        Switch(
          value: isActive,
          onChanged: (v) => controller.toggleSim(index, v),
          activeColor: AppTheme.primaryColor,
        ),
      ],
    ),
  );
}

Row的最后一个子组件是Switch开关。value绑定isActive状态,onChanged回调调用controller的toggleSim方法切换状态。
activeColor设置开关启用时的颜色为主色调。Switch是Flutter原生的开关组件,符合Material Design规范。

SIM卡流量使用

流量使用区域展示该SIM卡的本月使用量、套餐余量和使用进度:

Widget _buildSimUsage(Map<String, dynamic> sim) {
  final usage = sim['usage'] as String;
  final remaining = sim['remaining'] as String;
  final usagePercent = sim['usagePercent'] as double;
  
  return Padding(
    padding: EdgeInsets.all(16.w),

_buildSimUsage方法接收SIM卡数据Map,解构出使用量、剩余量、使用百分比三个字段。
Padding为整个区域添加16.w的内边距,和卡片头部保持一致的边距。

    child: Column(
      children: [
        Row(
          children: [
            Expanded(
              child: _buildUsageItem(
                '本月使用',
                usage,

Column垂直排列内容,Row横向排列"本月使用"和"套餐余量"两个数据项。Expanded让两个数据项各占一半宽度。
_buildUsageItem是封装的数据项组件,接收标签、数值、颜色三个参数。

                AppTheme.mobileColor,
              ),
            ),
            Container(width: 1, height: 40.h, color: Colors.grey.shade200),
            Expanded(
              child: _buildUsageItem(
                '套餐余量',
                remaining,
                AppTheme.wifiColor,
              ),
            ),
          ],
        ),

"本月使用"用橙色(mobileColor),"套餐余量"用绿色(wifiColor),颜色编码让用户快速区分。
中间用1像素宽、40高的灰色Container作为分隔线,视觉上分隔两个数据项。

        SizedBox(height: 16.h),
        Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '套餐使用进度',

SizedBox添加16.h的垂直间距。下方是进度条区域,先用Row显示标签和百分比,spaceBetween让它们分居两端。
"套餐使用进度"标签在左侧,百分比数值在右侧。

                  style: TextStyle(fontSize: 13.sp, color: AppTheme.textSecondary),
                ),
                Text(
                  '${usagePercent.toStringAsFixed(1)}%',
                  style: TextStyle(
                    fontSize: 14.sp,
                    fontWeight: FontWeight.bold,

标签使用13sp字号和次要颜色。百分比使用14sp字号和粗体,toStringAsFixed(1)保留一位小数。
百分比数值比标签稍大且加粗,突出显示当前使用进度。

                    color: usagePercent >= 80 ? Colors.orange : AppTheme.primaryColor,
                  ),
                ),
              ],
            ),
            SizedBox(height: 8.h),
            ClipRRect(
              borderRadius: BorderRadius.circular(4.r),

百分比颜色根据数值变化:超过80%显示橙色警告,否则显示主色调蓝色。这种颜色变化提醒用户注意流量使用情况。
ClipRRect用于裁剪子组件,给进度条添加圆角效果。LinearProgressIndicator本身不支持圆角,需要用ClipRRect包裹。

              child: LinearProgressIndicator(
                value: usagePercent / 100,
                backgroundColor: Colors.grey.shade200,
                valueColor: AlwaysStoppedAnimation(
                  usagePercent >= 80 ? Colors.orange : AppTheme.primaryColor,
                ),
                minHeight: 8.h,
              ),
            ),
          ],
        ),
      ],
    ),
  );
}

LinearProgressIndicator是Flutter的线性进度条。value需要0-1之间的值,所以usagePercent除以100。
backgroundColor设置进度条背景色,valueColor设置进度色,同样根据80%阈值变化颜色。minHeight设置进度条高度为8.h。

流量数据项的封装组件:

Widget _buildUsageItem(String label, String value, Color color) {
  return Column(
    children: [
      Text(label, style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary)),
      SizedBox(height: 4.h),
      Text(
        value,

_buildUsageItem封装了重复的UI结构,接收标签、数值、颜色三个参数。Column垂直排列标签和数值。
标签使用12sp小字号和次要颜色,SizedBox添加4.h的间距。

        style: TextStyle(
          fontSize: 18.sp,
          fontWeight: FontWeight.bold,
          color: color,
        ),
      ),
    ],
  );
}

数值使用18sp大字号、粗体、传入的颜色。这种封装避免了代码重复,修改样式时只需改一处。
标签在上、数值在下的布局,配合字号和颜色的差异,形成清晰的主次关系。

SIM卡操作按钮

操作按钮区域提供三个常用功能的快捷入口:

Widget _buildSimActions(int index, Map<String, dynamic> sim) {
  return Padding(
    padding: EdgeInsets.all(12.w),
    child: Row(
      children: [
        Expanded(
          child: _buildActionButton(

_buildSimActions方法接收卡槽索引和SIM卡数据。Padding设置12.w的内边距,比其他区域稍小因为按钮本身有内边距。
Row横向排列三个按钮,每个按钮用Expanded包裹实现等宽分布。

            Icons.sim_card,
            '套餐详情',
            () => _showPlanDetail(sim),
          ),
        ),
        SizedBox(width: 12.w),
        Expanded(
          child: _buildActionButton(
            Icons.history,

第一个按钮是"套餐详情",使用SIM卡图标,点击回调传入sim数据显示套餐详情弹窗。
SizedBox添加12.w的水平间距分隔按钮。第二个按钮是"使用记录",使用历史图标。

            '使用记录',
            () => _showUsageHistory(index),
          ),
        ),
        SizedBox(width: 12.w),
        Expanded(
          child: _buildActionButton(
            Icons.settings,
            '卡片设置',
            () => _showSimSettings(index),
          ),
        ),
      ],
    ),
  );
}

"使用记录"和"卡片设置"按钮的回调传入卡槽索引,用于获取对应卡槽的数据。
三个按钮等宽排列,间距12.w,形成整齐的操作区域。

单个操作按钮的封装:

Widget _buildActionButton(IconData icon, String label, VoidCallback onTap) {
  return GestureDetector(
    onTap: onTap,
    child: Container(
      padding: EdgeInsets.symmetric(vertical: 12.h),
      decoration: BoxDecoration(
        color: Colors.grey.shade50,

_buildActionButton封装单个按钮,接收图标、标签、点击回调三个参数。GestureDetector处理点击事件。
Container设置垂直内边距12.h,背景色使用非常浅的灰色(grey.shade50),和白色卡片背景有微妙区分。

        borderRadius: BorderRadius.circular(10.r),
      ),
      child: Column(
        children: [
          Icon(icon, color: AppTheme.primaryColor, size: 22.sp),
          SizedBox(height: 4.h),
          Text(
            label,

圆角10.r让按钮看起来更柔和。Column垂直排列图标和文字,图标在上文字在下是移动端常见的按钮样式。
图标使用主色调,大小22sp。SizedBox添加4.h的间距。

            style: TextStyle(fontSize: 12.sp, color: AppTheme.textSecondary),
          ),
        ],
      ),
    ),
  );
}

标签文字使用12sp小字号和次要颜色,作为图标的补充说明。
这种图标+文字的按钮设计直观易懂,用户一眼就能理解按钮功能。

空卡槽显示

当某个卡槽没有插入SIM卡时,显示简化的空状态卡片:

Widget _buildEmptySimSlot(int index) {
  return Container(
    padding: EdgeInsets.all(24.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
      border: Border.all(color: Colors.grey.shade300, style: BorderStyle.solid),

空卡槽使用24.w的内边距,比有卡的卡片(16.w)更大,因为内容更少需要更多留白。
使用灰色边框而不是阴影,和有卡的卡片形成视觉区分,暗示这是"空"的状态。

    ),
    child: Row(
      children: [
        Container(
          width: 56.w,
          height: 56.w,
          decoration: BoxDecoration(
            color: Colors.grey.shade100,

Row横向排列图标和文字。图标容器大小和有卡的卡片保持一致(56x56),背景使用浅灰色。
保持尺寸一致让两种状态的卡片在视觉上对齐。

            borderRadius: BorderRadius.circular(14.r),
          ),
          child: Icon(Icons.sim_card_outlined, color: Colors.grey, size: 32.sp),
        ),
        SizedBox(width: 16.w),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,

使用空心版本的SIM卡图标(sim_card_outlined),进一步强调"空"的概念。图标颜色为灰色。
SizedBox添加间距,Expanded让文字区域占据剩余空间,Column垂直排列文字并左对齐。

            children: [
              Text(
                'SIM ${index + 1}',
                style: TextStyle(
                  fontSize: 18.sp,
                  fontWeight: FontWeight.bold,
                  color: Colors.grey,
                ),
              ),

显示卡槽名称"SIM 1"或"SIM 2",字号和有卡的卡片一致,但颜色改为灰色表示空状态。
粗体保持标题的视觉重量,灰色降低其存在感。

              SizedBox(height: 4.h),
              Text(
                '未插入SIM卡',
                style: TextStyle(fontSize: 14.sp, color: AppTheme.textSecondary),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

下方显示"未插入SIM卡"提示文字,让用户明确知道这个卡槽的状态。
整个空卡槽设计简洁,只有必要的信息,没有开关和操作按钮。

默认数据卡设置

默认数据卡设置让用户选择哪张SIM卡作为移动数据的默认卡:

Widget _buildDefaultDataCard() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(16.r),
    ),

Container创建白色圆角卡片,内边距16.w,圆角16.r,和其他卡片保持一致的样式。
这个卡片用于选择默认使用移动数据的SIM卡。

    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '默认数据卡',
          style: TextStyle(
            fontSize: 16.sp,
            fontWeight: FontWeight.w600,

Column垂直排列内容,crossAxisAlignment设置左对齐。标题"默认数据卡"使用16sp字号和半粗体。
这是卡片的主标题,告诉用户这个区域的功能。

            color: AppTheme.textPrimary,
          ),
        ),
        SizedBox(height: 4.h),
        Text(
          '选择默认使用移动数据的SIM卡',
          style: TextStyle(fontSize: 13.sp, color: AppTheme.textSecondary),
        ),
        SizedBox(height: 16.h),

标题下方是说明文字,13sp字号,次要颜色,解释这个设置的作用。
SizedBox添加16.h的间距,分隔说明文字和选项按钮。

        Obx(() => Row(
          children: [
            Expanded(
              child: _buildDataCardOption(0, 'SIM 1', controller.defaultDataSim.value == 0),
            ),
            SizedBox(width: 12.w),
            Expanded(
              child: _buildDataCardOption(1, 'SIM 2', controller.defaultDataSim.value == 1),
            ),
          ],
        )),
      ],
    ),
  );
}

Obx包裹选项Row,响应defaultDataSim的变化。两个选项用Expanded等宽排列,间距12.w。
_buildDataCardOption接收索引、标签、是否选中三个参数,根据defaultDataSim.value判断选中状态。

单个选项按钮的实现:

Widget _buildDataCardOption(int index, String label, bool isSelected) {
  return GestureDetector(
    onTap: () => controller.setDefaultDataSim(index),
    child: Container(
      padding: EdgeInsets.symmetric(vertical: 16.h),
      decoration: BoxDecoration(

GestureDetector处理点击事件,调用controller的setDefaultDataSim方法设置默认卡。
Container设置垂直内边距16.h,让按钮有足够的点击区域。

        color: isSelected ? AppTheme.primaryColor.withOpacity(0.1) : Colors.grey.shade50,
        borderRadius: BorderRadius.circular(12.r),
        border: Border.all(
          color: isSelected ? AppTheme.primaryColor : Colors.transparent,
          width: 2,
        ),
      ),

选中状态:浅蓝色背景、蓝色边框。未选中状态:浅灰色背景、透明边框(保持2像素宽度避免布局跳动)。
这种设计让选中和未选中状态有明显的视觉区分。

      child: Column(
        children: [
          Icon(
            Icons.sim_card,
            color: isSelected ? AppTheme.primaryColor : Colors.grey,
            size: 28.sp,
          ),
          SizedBox(height: 8.h),

Column垂直排列图标和文字。图标颜色根据选中状态变化,选中时蓝色,未选中时灰色。
图标大小28sp,比操作按钮的图标稍大,因为这是主要的选择区域。

          Text(
            label,
            style: TextStyle(
              fontSize: 14.sp,
              fontWeight: FontWeight.w600,
              color: isSelected ? AppTheme.primaryColor : AppTheme.textSecondary,
            ),
          ),
          if (isSelected) ...[

标签文字14sp,半粗体,颜色同样根据选中状态变化。
if (isSelected)配合展开运算符,只有选中的选项才显示"默认"标签。

            SizedBox(height: 4.h),
            Text(
              '默认',
              style: TextStyle(fontSize: 11.sp, color: AppTheme.primaryColor),
            ),
          ],
        ],
      ),
    ),
  );
}

"默认"标签使用11sp小字号和主色调,进一步强调当前选择。
整个选项设计:图标在上,SIM名称在中,选中时底部显示"默认"标签。

总流量统计

页面底部展示双卡的汇总数据:

Widget _buildTotalUsage() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [AppTheme.primaryColor, AppTheme.primaryColor.withOpacity(0.8)],
      ),

Container使用渐变背景,从主色调到80%透明度的主色调,形成从左到右逐渐变浅的效果。
渐变背景让这个汇总卡片在视觉上更突出,和上方的白色卡片形成对比。

      borderRadius: BorderRadius.circular(16.r),
    ),
    child: Obx(() => Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        _buildTotalItem('双卡总使用', controller.totalUsage.value),
        Container(width: 1, height: 40.h, color: Colors.white30),

圆角16.r保持一致。Obx响应totalUsage和totalRemaining的变化。Row横向排列两个数据项。
spaceAround让数据项均匀分布,两端也有间距。中间用30%透明度的白色分隔线。

        _buildTotalItem('双卡总余量', controller.totalRemaining.value),
      ],
    )),
  );
}

两个数据项分别显示双卡总使用量和总余量,让用户了解整体的流量情况。
半透明白色分隔线在深色背景上既能看清又不会太突兀。

单个统计项的封装:

Widget _buildTotalItem(String label, String value) {
  return Column(
    children: [
      Text(label, style: TextStyle(fontSize: 12.sp, color: Colors.white70)),
      SizedBox(height: 4.h),
      Text(
        value,

Column垂直排列标签和数值。标签使用12sp字号和70%透明度的白色,在深色背景上形成次要层次。
SizedBox添加4.h的间距。

        style: TextStyle(
          fontSize: 20.sp,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      ),
    ],
  );
}

数值使用20sp大字号、粗体、纯白色,是视觉焦点。
标签70%白色、数值纯白色的对比,在深色背景上形成清晰的主次关系。

Controller实现

Controller负责管理SIM卡数据和处理用户操作:

class SimManagerController extends GetxController {
  final simCards = <Map<String, dynamic>>[].obs;
  final defaultDataSim = 0.obs;
  final totalUsage = '0 GB'.obs;
  final totalRemaining = '0 GB'.obs;

声明四个响应式变量:simCards是SIM卡数据列表,defaultDataSim是默认数据卡索引,totalUsage和totalRemaining是汇总数据。
.obs将普通变量转换为响应式变量,数据变化时UI自动更新。

  
  void onInit() {
    super.onInit();
    loadSimCards();
  }

  void loadSimCards() {
    simCards.value = [
      {

onInit是GetX Controller的生命周期方法,在Controller创建后立即调用。这里调用loadSimCards加载数据。
loadSimCards方法设置模拟的SIM卡数据,实际项目中应该调用系统API获取真实数据。

        'carrier': '中国移动',
        'active': true,
        'usage': '6.5 GB',
        'remaining': '3.5 GB',
        'usagePercent': 65.0,
        'phoneNumber': '138****1234',
      },

第一张SIM卡的模拟数据:中国移动运营商,已启用,使用了6.5GB,剩余3.5GB,使用率65%,手机号脱敏显示。
这些字段对应UI中显示的各项信息。

      {
        'carrier': '中国联通',
        'active': false,
        'usage': '0 B',
        'remaining': '10 GB',
        'usagePercent': 0.0,
        'phoneNumber': '186****5678',
      },
    ];
    calculateTotal();
  }

第二张SIM卡:中国联通,未启用,未使用流量,剩余10GB。加载完数据后调用calculateTotal计算汇总。
两张卡一启用一禁用,方便测试不同状态的UI显示。

  void toggleSim(int index, bool value) {
    simCards[index]['active'] = value;
    simCards.refresh();
    
    if (value) {
      Get.snackbar('SIM卡', 'SIM ${index + 1} 已启用');
    } else {

toggleSim方法切换SIM卡的启用状态。修改Map中的值后调用refresh()通知GetX数据已变化。
根据新状态显示不同的Snackbar提示,让用户确认操作结果。

      Get.snackbar('SIM卡', 'SIM ${index + 1} 已禁用');
    }
  }

  void setDefaultDataSim(int index) {
    if (!simCards[index]['active']) {
      Get.snackbar('提示', '请先启用该SIM卡');
      return;
    }

setDefaultDataSim方法设置默认数据卡。首先检查目标卡是否已启用,如果未启用则显示提示并返回。
这个验证逻辑防止用户将禁用的卡设为默认,避免逻辑错误。

    defaultDataSim.value = index;
    Get.snackbar('设置成功', 'SIM ${index + 1} 已设为默认数据卡');
  }

  void calculateTotal() {
    totalUsage.value = '6.5 GB';
    totalRemaining.value = '13.5 GB';
  }

设置成功后更新defaultDataSim并显示成功提示。calculateTotal计算双卡总使用量和总余量。
实际项目中需要解析各卡的数据字符串,提取数值求和后格式化。这里用硬编码的模拟数据。

  void refreshSimInfo() {
    Get.snackbar('刷新中', '正在获取SIM卡信息...');
    loadSimCards();
  }
}

refreshSimInfo方法响应刷新按钮点击,显示加载提示后重新加载数据。
实际项目中这里应该是异步操作,需要加loading状态和错误处理。

写在最后

SIM卡管理页面帮助双卡用户更好地管理和分配流量使用。通过清晰的卡片展示、便捷的开关控制、直观的流量统计,用户可以轻松掌握每张卡的使用情况。

可以继续优化的方向:

  • 支持智能切换数据卡
  • 添加流量共享功能
  • 支持按应用指定使用的SIM卡
  • 添加SIM卡流量对比图表

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐