在这里插入图片描述

1. 这个功能解决什么问题

离线数据管理是井盖地图APP的核心实用功能,主要解决以下场景的用户需求:

  • 数据统计:精准展示本地缓存总大小、各类数据条目数量,让用户清晰知晓存储空间占用
  • 缓存管理:按类型分类展示缓存占用,区分井盖数据、工单数据、图片缓存等维度
  • 清理操作:提供安全的一键清理功能,附带二次确认机制避免误操作
  • 模拟数据:开发阶段使用固定统计数据,便于功能调试和UI预览

这个页面是典型的"数据管理"场景,既满足用户对本地数据的掌控需求,也是ListTile + AlertDialog 组件组合的最佳实践示例。

2. 相关文件一览

  • lib/feature_pages.dartOfflineDataPage):离线数据管理页面的核心实现文件,包含UI渲染、交互逻辑等全部内容

3. 数据统计展示

页面核心采用ListView作为基础布局容器,具备以下设计考量:

  • 外层ListView设置12px统一内边距,保证页面左右留白均衡
  • 每个数据统计项使用Card包裹,增加视觉层次感和点击反馈边界
  • ListTile组件分三部分布局:leading图标、title标题、trailing统计值
ListView(
  padding: const EdgeInsets.all(12),
  children: [
    Card(
      child: ListTile(
        leading: const Icon(Icons.storage),
        title: const Text('缓存大小'),
        trailing: const Text('12.3 MB'),
      ),
    ),

针对不同数据类型的视觉区分设计:

  • 缓存大小使用Icons.storage存储图标,直观关联存储空间概念
  • 井盖数据使用Icons.list_alt列表图标,对应条目类数据展示
  • 工单数据使用Icons.assignment任务图标,贴合工单业务属性
  • 图片缓存使用Icons.photo_library图库图标,匹配图片类缓存特征
    Card(
      child: ListTile(
        leading: const Icon(Icons.list_alt),
        title: const Text('井盖数据'),
        trailing: const Text('18 条'),
      ),
    ),
    Card(
      child: ListTile(
        leading: const Icon(Icons.assignment),
        title: const Text('工单数据'),
        trailing: const Text('24 条'),
      ),
    ),

图片缓存项的实现细节说明:

  • 图标选用与图片场景强关联的Icons.photo_library
  • trailing文本采用"数值 + 单位"的统一格式,保持视觉一致性
  • Card组件默认自带圆角和阴影,无需额外样式调整
    Card(
      child: ListTile(
        leading: const Icon(Icons.photo_library),
        title: const Text('图片缓存'),
        trailing: const Text('8.7 MB'),
      ),
    ),
  ],
)

4. 清理操作

清理缓存功能的交互设计原则:

  • 危险操作视觉强化:使用红色图标区分普通操作和危险操作
  • 操作提示分层:标题说明功能,副标题补充操作后果
  • 二次确认机制:点击后弹出对话框,避免用户误触
Card(
  child: ListTile(
    leading: const Icon(Icons.delete_sweep, color: Colors.red),
    title: const Text('清理缓存'),
    subtitle: const Text('删除所有离线数据'),

点击事件的核心逻辑设计:

  • 调用showDialog弹出确认对话框,上下文传递当前页面context
  • 对话框使用AlertDialog组件,遵循Material Design规范
  • 对话框内部分为标题、内容、操作按钮三部分,结构清晰
    onTap: () {
      showDialog(
        context: context,
        builder: (_) => AlertDialog(
          title: const Text('确认清理'),
          content: const Text('确定要清理所有离线数据吗?此操作不可恢复。'),

对话框按钮的交互逻辑设计:

  • 取消按钮:仅关闭对话框,无其他副作用
  • 确定按钮:先关闭对话框,再展示操作结果提示
  • 使用SnackBar反馈操作结果,符合移动端操作反馈习惯
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('取消'),
            ),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
                ScaffoldMessenger.of(context)
                    .showSnackBar(const SnackBar(content: Text('缓存已清理(模拟)')));
              },
              child: const Text('确定'),
            ),
          ],
        ),
      );
    },
  ),
)

5. 完整页面代码(工程真实内容)

页面类的基础结构设计:

  • 采用StatelessWidget,因页面数据为静态模拟数据
  • 构造函数使用super.key,遵循Flutter最佳实践
  • 类名采用大驼峰命名法,符合Dart代码规范
class OfflineDataPage extends StatelessWidget {
  const OfflineDataPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('离线数据')),

页面主体布局的层级设计:

  • 外层Scaffold提供基础页面结构(导航栏+内容区)
  • 内容区使用ListView支持纵向滚动,适配小屏设备
  • 统一设置12px内边距,保证所有子组件的间距一致性
      body: ListView(
        padding: const EdgeInsets.all(12),
        children: [
          Card(
            child: ListTile(
              leading: const Icon(Icons.storage),
              title: const Text('缓存大小'),
              trailing: const Text('12.3 MB'),
            ),
          ),

井盖数据项的实现要点:

  • ListTile的leading属性放置功能图标,增强视觉识别
  • title属性为数据类型名称,采用简洁文本
  • trailing属性展示具体数值,与title形成左右对比布局
          Card(
            child: ListTile(
              leading: const Icon(Icons.list_alt),
              title: const Text('井盖数据'),
              trailing: const Text('18 条'),
            ),
          ),

工单数据项的设计细节:

  • 图标选择与工单业务场景匹配,提升用户认知效率
  • 数值展示采用"数字 + 单位"格式,保持数据展示一致性
  • Card组件自动适配父容器宽度,无需手动设置尺寸
          Card(
            child: ListTile(
              leading: const Icon(Icons.assignment),
              title: const Text('工单数据'),
              trailing: const Text('24 条'),
            ),
          ),

图片缓存项的布局说明:

  • 图标颜色使用系统默认主色,保持视觉统一性
  • 文本使用默认样式,无需额外设置字体大小
  • Card组件之间自动分隔,无需手动添加间距
          Card(
            child: ListTile(
              leading: const Icon(Icons.photo_library),
              title: const Text('图片缓存'),
              trailing: const Text('8.7 MB'),
            ),
          ),

清理缓存项的间距设计:

  • 使用SizedBox设置12px垂直间距,区分数据统计区和操作区
  • 间距值与页面整体内边距保持一致,保证布局节奏感
  • 视觉上分隔不同功能区域,提升页面可读性
          const SizedBox(height: 12),
          Card(
            child: ListTile(
              leading: const Icon(Icons.delete_sweep, color: Colors.red),
              title: const Text('清理缓存'),

清理缓存项的文本设计:

  • 主标题明确操作名称,副标题补充操作影响
  • 红色图标传递危险操作提示,符合用户操作习惯
  • 文本与图标左对齐,保持ListTile组件的布局规范
              subtitle: const Text('删除所有离线数据'),
              onTap: () {
                showDialog(
                  context: context,
                  builder: (_) => AlertDialog(
                    title: const Text('确认清理'),

确认对话框的内容设计:

  • 标题使用"确认+操作名称"格式,清晰传达对话框用途
  • 内容文本明确提示操作不可恢复,强化用户风险认知
  • 文本行高使用系统默认值,保证可读性
                    content: const Text('确定要清理所有离线数据吗?此操作不可恢复。'),
                    actions: [
                      TextButton(
                        onPressed: () => Navigator.pop(context),
                        child: const Text('取消'),
                      ),

确认按钮的逻辑设计:

  • 先关闭对话框,再执行清理反馈
  • 使用ScaffoldMessenger展示操作结果,全局提示更醒目
  • SnackBar文本明确标注"模拟",区分开发阶段和生产环境
                      TextButton(
                        onPressed: () {
                          Navigator.pop(context);
                          ScaffoldMessenger.of(context)
                              .showSnackBar(const SnackBar(content: Text('缓存已清理(模拟)')));
                        },
                        child: const Text('确定'),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

6. 离线数据模型

离线数据模型的设计思路:

  • 采用面向对象思想,封装离线数据的所有核心属性
  • 必选属性使用required关键字,保证数据完整性
  • 可选属性使用可空类型,适配不同数据场景
class OfflineData {
  final String id;
  final String name;
  final String description;
  final DataType dataType;
  final double size;
  final int count;

时间属性的设计考量:

  • createdAt为必选,记录数据首次缓存时间
  • lastAccessedlastModified为可选,记录数据使用状态
  • 使用DateTime类型,便于时间计算和格式化展示
  final DateTime createdAt;
  final DateTime? lastAccessed;
  final DateTime? lastModified;
  final String? version;
  final bool isEncrypted;
  final DataStatus status;
  final Map<String, dynamic> metadata;

构造函数的设计规范:

  • 使用const构造函数,提升不可变对象的性能
  • 所有属性通过参数传入,保证数据封装性
  • metadata默认值为空Map,简化实例化操作
  const OfflineData({
    required this.id,
    required this.name,
    required this.description,
    required this.dataType,
    required this.size,
    required this.count,
    required this.createdAt,
    this.lastAccessed,
    this.lastModified,
    this.version,
    required this.isEncrypted,
    required this.status,
    this.metadata = const {},
  });
}

缓存信息模型的设计重点:

  • 聚焦缓存管理场景,属性贴合缓存操作需求
  • cacheKey作为唯一标识,用于缓存增删改查
  • displayName用于UI展示,区分技术标识和用户友好名称
class CacheInfo {
  final String cacheKey;
  final String displayName;
  final CacheType cacheType;
  final double totalSize;
  final int itemCount;

缓存生命周期属性设计:

  • createdAt记录缓存创建时间
  • lastUsed记录最后使用时间,用于缓存淘汰策略
  • maxAge设置缓存最大存活时间,单位为秒
  final DateTime createdAt;
  final DateTime? lastUsed;
  final bool isAutoCleanable;
  final int maxAge;
  final String? storagePath;

  const CacheInfo({
    required this.cacheKey,
    required this.displayName,
    required this.cacheType,
    required this.totalSize,
    required this.itemCount,

缓存信息构造函数:

  • 必选属性强制传入,保证缓存基础信息完整
  • 可选属性按需设置,适配不同缓存类型
  • 无默认值的可选属性默认为null,符合Dart空安全规范
    required this.createdAt,
    this.lastUsed,
    required this.isAutoCleanable,
    required this.maxAge,
    this.storagePath,
  });
}

存储使用情况模型的设计目标:

  • 统计设备整体存储状态,而非单个缓存项
  • 包含总量、已用、可用等核心指标
  • 提供计算属性,简化UI层的百分比计算
class StorageUsage {
  final double totalSpace;
  final double usedSpace;
  final double freeSpace;
  final double cacheSize;
  final double dataSize;
  final double tempSize;

存储统计的时间和分布属性:

  • calculatedAt记录统计时间,保证数据时效性
  • dataDistribution按数据类型统计占用,便于分类展示
  • Map键为DataType枚举,值为占用大小,类型安全
  final DateTime calculatedAt;
  final Map<DataType, double> dataDistribution;

  const StorageUsage({
    required this.totalSpace,
    required this.usedSpace,
    required this.freeSpace,
    required this.cacheSize,
    required this.dataSize,

存储模型构造函数:

  • 所有属性强制传入,保证统计数据完整
  • 无可选属性,避免统计数据缺失
  • 构造函数参数顺序按重要性排列,提升可读性
    required this.tempSize,
    required this.calculatedAt,
    required this.dataDistribution,
  });

计算属性的实现逻辑:

  • usagePercentage计算整体使用率,取值范围0-1
  • cachePercentage计算缓存占已用空间比例
  • 使用三元运算符处理分母为0的边界情况
  double get usagePercentage => totalSpace > 0 ? usedSpace / totalSpace : 0.0;
  double get cachePercentage => usedSpace > 0 ? cacheSize / usedSpace : 0.0;
}

数据类型枚举的设计:

  • 按业务场景分类,覆盖APP核心数据类型
  • 枚举值使用小写+下划线命名法,符合Dart规范
  • 每个枚举值对应具体的业务数据,语义清晰
enum DataType {
  manhole,
  workOrder,
  inspection,
  report,
  map,
  user,
  settings,
  temp,
}

数据状态枚举的设计考量:

  • 覆盖数据全生命周期状态
  • 状态值简洁明确,便于业务逻辑判断
  • 与后端数据状态保持一致,便于同步
enum DataStatus {
  active,
  archived,
  corrupted,
  syncing,
  pending,
}

缓存类型枚举的设计:

  • 按存储介质和类型分类
  • 匹配实际缓存实现方式(内存、磁盘、数据库等)
  • 便于后续按类型执行不同的清理策略
enum CacheType {
  memory,
  disk,
  database,
  image,
  file,
}

同步状态枚举的设计:

  • 覆盖数据同步的核心状态
  • 包含成功、待同步、冲突、错误等场景
  • 便于UI层展示同步状态和处理同步异常
enum SyncStatus {
  synced,
  pending,
  conflict,
  error,
}

7. 离线数据状态管理

状态管理类的基础设计:

  • 继承ChangeNotifier,实现数据变更通知
  • 使用私有变量存储状态,通过getter暴露数据
  • 所有状态变量初始值合理,避免空指针
class OfflineDataProvider extends ChangeNotifier {
  List<OfflineData> _offlineData = [];
  List<CacheInfo> _cacheInfo = [];
  StorageUsage? _storageUsage;
  bool _loading = false;
  String? _error;
  DateTime? _lastRefreshTime;
  bool _isCleaning = false;

Getter方法的设计规范:

  • 只读属性,无对应的setter方法
  • 直接返回私有变量,保证数据封装性
  • 命名简洁,与私有变量名称对应
  List<OfflineData> get offlineData => _offlineData;
  List<CacheInfo> get cacheInfo => _cacheInfo;
  StorageUsage? get storageUsage => _storageUsage;
  bool get loading => _loading;
  String? get error => _error;
  DateTime? get lastRefreshTime => _lastRefreshTime;
  bool get isCleaning => _isCleaning;

数据加载方法的核心逻辑:

  • 加载前设置loading状态,更新UI为加载中
  • 清空错误信息,避免旧错误干扰
  • 调用notifyListeners通知UI更新
  Future<void> loadOfflineData() async {
    _loading = true;
    _error = null;
    notifyListeners();

    try {
      await Future.delayed(const Duration(seconds: 1));

模拟数据加载的实现:

  • 使用Future.delayed模拟网络/本地加载延迟
  • 调用私有方法生成模拟数据,代码结构清晰
  • 记录最后刷新时间,便于展示数据新鲜度
      _offlineData = _generateMockOfflineData();
      _cacheInfo = _generateMockCacheInfo();
      _storageUsage = _calculateStorageUsage();
      _lastRefreshTime = DateTime.now();
      
      _loading = false;
      notifyListeners();

异常处理的设计:

  • catch块捕获所有异常,避免崩溃
  • 记录错误信息,便于UI层展示
  • 重置loading状态,保证UI恢复可操作
    } catch (e) {
      _error = e.toString();
      _loading = false;
      notifyListeners();
    }
  }

缓存清理方法的参数设计:

  • cleanAll布尔值控制是否清理全部缓存
  • specificCaches列表指定要清理的缓存项
  • 参数具有默认值,简化调用
  Future<void> cleanCache({bool cleanAll = false, List<String>? specificCaches}) async {
    _isCleaning = true;
    notifyListeners();

    try {
      await Future.delayed(const Duration(seconds: 2));

全量清理的逻辑实现:

  • 清空缓存信息列表,重置缓存数据
  • 更新存储使用信息,重置缓存大小为0
  • 重新计算已用空间,排除缓存占用
      if (cleanAll) {
        _cacheInfo.clear();
        _storageUsage = _storageUsage?.copyWith(
          cacheSize: 0,
          usedSpace: _storageUsage!.dataSize + _storageUsage!.tempSize,
        );

指定缓存清理的逻辑:

  • 根据cacheKey过滤要删除的缓存项
  • 重新计算存储使用情况,保证数据一致性
  • 仅删除指定项,保留其他缓存数据
      } else if (specificCaches != null) {
        _cacheInfo.removeWhere((cache) => specificCaches.contains(cache.cacheKey));
        _storageUsage = _calculateStorageUsage();
      }
      
      _isCleaning = false;
      notifyListeners();

清理操作的异常处理:

  • 捕获清理过程中的异常,记录错误信息
  • 重置清理状态,保证UI恢复正常
  • 通知UI更新状态,展示清理结果
    } catch (e) {
      _error = e.toString();
      _isCleaning = false;
      notifyListeners();
    }
  }

数据刷新方法的设计:

  • 简单封装,复用loadOfflineData逻辑
  • 无额外参数,调用便捷
  • 便于UI层绑定刷新按钮点击事件
  Future<void> refreshData() async {
    await loadOfflineData();
  }

模拟离线数据生成方法:

  • 使用固定随机种子,保证数据可复现
  • 按业务场景生成井盖数据和工单数据
  • 时间属性使用随机偏移,模拟真实使用场景
  List<OfflineData> _generateMockOfflineData() {
    final rng = Random(42);
    return [
      OfflineData(
        id: 'data_1',
        name: '井盖数据',
        description: '所有井盖的基本信息和状态',
        dataType: DataType.manhole,
        size: 8.5,

井盖数据模拟的细节:

  • 数量和大小设置符合真实业务场景
  • 时间属性使用随机天数/小时数偏移
  • 版本号模拟真实版本管理
        count: 150,
        createdAt: DateTime.now().subtract(Duration(days: rng.nextInt(30))),
        lastAccessed: DateTime.now().subtract(Duration(hours: rng.nextInt(24))),
        version: '1.2.0',
        isEncrypted: false,
        status: DataStatus.active,

元数据的设计:

  • 使用Map存储扩展属性,灵活性高
  • 包含同步状态等业务属性
  • 时间属性存储为ISO8601字符串,便于序列化
        metadata: {'synced': true, 'lastSync': DateTime.now().toIso8601String()},
      ),
      OfflineData(
        id: 'data_2',
        name: '工单数据',
        description: '所有工单的详细信息',
        dataType: DataType.workOrder,
        size: 12.3,

工单数据模拟的特点:

  • 启用加密属性,模拟敏感数据
  • 同步状态设置为未同步,模拟真实业务场景
  • 元数据包含待同步数量,便于后续同步处理
        count: 85,
        createdAt: DateTime.now().subtract(Duration(days: rng.nextInt(30))),
        lastAccessed: DateTime.now().subtract(Duration(hours: rng.nextInt(24))),
        version: '1.1.5',
        isEncrypted: true,
        status: DataStatus.active,
        metadata: {'synced': false, 'pendingSync': 12},
      ),
    ];
  }

模拟缓存信息生成方法:

  • 按缓存类型分类生成(图片、地图、数据库)
  • 大小和数量设置符合不同缓存类型的特征
  • 自动清理标识区分不同缓存策略
  List<CacheInfo> _generateMockCacheInfo() {
    final rng = Random(42);
    return [
      CacheInfo(
        cacheKey: 'image_cache',
        displayName: '图片缓存',
        cacheType: CacheType.image,
        totalSize: 45.6,
        itemCount: 234,

图片缓存的特征设置:

  • 数量多、单条小,符合图片缓存特点
  • 启用自动清理,设置7天最大存活时间
  • 指定存储路径,便于定位缓存文件
        createdAt: DateTime.now().subtract(Duration(days: rng.nextInt(30))),
        lastUsed: DateTime.now().subtract(Duration(hours: rng.nextInt(24))),
        isAutoCleanable: true,
        maxAge: 7 * 24 * 60 * 60, // 7 days
        storagePath: '/data/cache/images',
      ),
      CacheInfo(
        cacheKey: 'map_cache',
        displayName: '地图缓存',
        cacheType: CacheType.file,
        totalSize: 28.3,

地图缓存的设计特点:

  • 数量少、单条大,符合地图瓦片特征
  • 禁用自动清理,重要性高于图片缓存
  • 最大存活时间设置为30天,适配地图数据更新频率
        itemCount: 45,
        createdAt: DateTime.now().subtract(Duration(days: rng.nextInt(30))),
        lastUsed: DateTime.now().subtract(Duration(hours: rng.nextInt(24))),
        isAutoCleanable: false,
        maxAge: 30 * 24 * 60 * 60, // 30 days
        storagePath: '/data/cache/maps',
      ),
      CacheInfo(
        cacheKey: 'database_cache',
        displayName: '数据库缓存',
        cacheType: CacheType.database,

数据库缓存的参数设置:

  • 条目数量大,符合数据库缓存特征
  • 启用自动清理,存活时间短(3天)
  • 存储路径指向数据库缓存目录
        totalSize: 15.8,
        itemCount: 1200,
        createdAt: DateTime.now().subtract(Duration(days: rng.nextInt(30))),
        lastUsed: DateTime.now().subtract(Duration(hours: rng.nextInt(24))),
        isAutoCleanable: true,
        maxAge: 3 * 24 * 60 * 60, // 3 days
        storagePath: '/data/cache/database',
      ),
    ];
  }

存储使用情况计算方法:

  • 总空间固定为1GB,模拟移动设备存储
  • 数据大小通过折叠离线数据列表计算
  • 缓存大小通过折叠缓存信息列表计算
  StorageUsage _calculateStorageUsage() {
    final totalSpace = 1024.0; // 1GB
    final dataSize = _offlineData.fold(0.0, (sum, data) => sum + data.size);
    final cacheSize = _cacheInfo.fold(0.0, (sum, cache) => sum + cache.totalSize);
    final tempSize = 5.2; // 临时文件大小
    final usedSpace = dataSize + cacheSize + tempSize;

存储使用模型实例化:

  • 计算可用空间(总空间-已用空间)
  • 记录计算时间,保证数据时效性
  • 构建数据分布Map,按类型统计大小
    return StorageUsage(
      totalSpace: totalSpace,
      usedSpace: usedSpace,
      freeSpace: totalSpace - usedSpace,
      cacheSize: cacheSize,
      dataSize: dataSize,
      tempSize: tempSize,
      calculatedAt: DateTime.now(),
      dataDistribution: {
        for (final data in _offlineData) data.dataType: data.size,
      },
    );
  }
}

8. 高级离线数据组件

高级组件的基础结构:

  • 继承StatefulWidget,支持生命周期管理
  • 类名使用大驼峰命名,后缀Widget明确组件类型
  • 构造函数使用super.key,遵循Flutter规范
class AdvancedOfflineDataWidget extends StatefulWidget {
  const AdvancedOfflineDataWidget({super.key});

  
  State<AdvancedOfflineDataWidget> createState() => _AdvancedOfflineDataWidgetState();
}

class _AdvancedOfflineDataWidgetState extends State<AdvancedOfflineDataWidget> {

初始化方法的设计:

  • initState中添加帧回调,保证UI构建完成后执行
  • 获取Provider实例,设置listen: false避免不必要重建
  • 调用数据加载方法,初始化页面数据
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final provider = Provider.of<OfflineDataProvider>(context, listen: false);
      provider.loadOfflineData();
    });
  }

构建方法的核心逻辑:

  • 使用Consumer监听Provider变化,自动重建UI
  • 根据loading状态展示加载指示器或内容
  • 导航栏添加清理和刷新按钮,提升操作便捷性
  
  Widget build(BuildContext context) {
    return Consumer<OfflineDataProvider>(
      builder: (context, provider, child) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('离线数据'),
            actions: [
              IconButton(
                onPressed: () => _showCleanDialog(context, provider),
                icon: const Icon(Icons.cleaning_services),
              ),
              IconButton(
                onPressed: provider.refreshData,
                icon: const Icon(Icons.refresh),
              ),
            ],
          ),

加载状态的UI处理:

  • loading为true时展示圆形加载指示器
  • 居中布局,符合移动端加载状态设计规范
  • loading为false时展示核心内容
          body: provider.loading
              ? const Center(child: CircularProgressIndicator())
              : _buildContent(context, provider),
        );
      },
    );
  }

内容构建方法的设计:

  • 使用RefreshIndicator支持下拉刷新
  • 垂直布局分为存储卡片、数据列表、缓存列表三部分
  • 各部分之间设置8px间距,保证视觉呼吸感
  Widget _buildContent(BuildContext context, OfflineDataProvider provider) {
    return RefreshIndicator(
      onRefresh: provider.refreshData,
      child: Column(
        children: [
          _buildStorageCard(context, provider),
          const SizedBox(height: 8),
          _buildDataList(context, provider),
          const SizedBox(height: 8),
          _buildCacheList(context, provider),
        ],
      ),
    );
  }

存储卡片的构建逻辑:

  • 空值判断,避免storage为null时崩溃
  • 使用Card组件包裹,提升视觉层次感
  • 16px内边距,保证内容不贴边
  Widget _buildStorageCard(BuildContext context, OfflineDataProvider provider) {
    final storage = provider.storageUsage;
    if (storage == null) return const SizedBox.shrink();
    
    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [

存储卡片的标题设计:

  • 使用主题文本样式,保证与系统风格一致
  • 设置加粗字体,突出标题层级
  • 标题文本简洁明确,传达卡片用途
            Text(
              '存储使用情况',
              style: Theme.of(context).textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),

进度条的属性设置:

  • 高度12px,圆角6px,符合现代设计风格
  • 进度值限制在0-1之间,避免异常显示
  • 背景色使用浅灰色,保证进度条可见
            LinearPercentIndicator(
              lineHeight: 12,
              percent: storage.usagePercentage.clamp(0.0, 1.0),
              progressColor: _getStorageColor(storage.usagePercentage),
              backgroundColor: Colors.grey.shade200,
              barRadius: const Radius.circular(6),
            ),
            const SizedBox(height: 8),

存储统计文本的布局:

  • 左右两端对齐,展示不同维度信息
  • 左侧展示已用/总空间,右侧展示使用率
  • 小号字体(12px),作为辅助信息
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '${storage.usedSpace.toStringAsFixed(1)} MB / ${storage.totalSpace.toStringAsFixed(1)} MB',
                  style: TextStyle(
                    color: Colors.grey.shade600,
                    fontSize: 12,
                  ),
                ),
                Text(
                  '${(storage.usagePercentage * 100).toStringAsFixed(1)}%',
                  style: TextStyle(
                    color: _getStorageColor(storage.usagePercentage),
                    fontWeight: FontWeight.bold,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

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

Logo

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

更多推荐