基础入门 Flutter for OpenHarmony:shared_preferences 轻量级存储详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 shared_preferences 轻量级存储的使用方法,带你从零开始掌握这一重要的数据持久化方案。


一、shared_preferences 组件概述

在 Flutter for OpenHarmony 应用开发中,shared_preferences 是一个非常实用的轻量级数据持久化方案。它提供了简单的键值对存储功能,类似于 Android 中的 SharedPreferences 和 iOS 中的 UserDefaults,适合存储用户的简单设置、配置信息等轻量级数据。

📋 shared_preferences 组件特点

特点 说明
键值对存储 使用简单的键值对方式存储数据
跨平台支持 支持 Android、iOS、Linux、macOS、Windows、OpenHarmony
异步操作 所有读写操作都是异步的,不会阻塞 UI
类型支持 支持 String、int、double、bool、List<String>
持久化 数据会持久保存,应用重启后仍然可用
简单易用 API 简洁直观,易于上手

💡 使用场景:保存用户设置(如主题、语言)、登录状态、开关状态、用户偏好配置等小型数据。不适合存储大量数据或复杂对象。


二、OpenHarmony 平台适配说明

本项目基于 shared_preferences@2.2.0 开发,适配 Flutter 3.27.5-ohos-1.0.4。

2.1 支持的数据类型

在 OpenHarmony 平台上,shared_preferences 支持以下数据类型:

数据类型 说明 OpenHarmony 支持
String 字符串值 ✅ yes
int 整数值 ✅ yes
double 浮点数值 ✅ yes
bool 布尔值 ✅ yes
List<String> 字符串列表 ✅ yes

三、项目配置与安装

3.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 shared_preferences 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

  # 添加 shared_preferences 依赖(从 git 引入,支持 OpenHarmony 平台)
  shared_preferences:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/shared_preferences/shared_preferences"

配置说明:

  • 使用 git 方式从 GitCode 仓库引入依赖
  • url 指向开源鸿蒙 TPC 维护的 flutter_packages 仓库
  • path 指定仓库中 shared_preferences 包的具体路径
  • 该版本已适配 OpenHarmony 平台,版本为 2.5.3

3.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

3.3 依赖自动配置说明

执行 flutter pub get 后,OpenHarmony 平台的依赖会自动配置到 ohos/entry/oh-package.json5 文件中。Flutter 构建系统会自动处理平台相关的依赖配置,无需手动干预。


四、shared_preferences 基础用法

4.1 获取 SharedPreferences 实例

在使用 shared_preferences 之前,首先需要获取其实例。这是一个异步操作:

dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';

// 获取 SharedPreferences 实例
final SharedPreferences prefs = await SharedPreferences.getInstance();

注意事项:

  • getInstance() 是一个异步方法,返回 Future<SharedPreferences>
  • 建议在应用启动时获取实例并缓存,避免重复获取
  • 在 StatefulWidget 中,可以在 initState 或其他合适的地方获取实例

4.2 存储数据

shared_preferences 为每种数据类型提供了对应的存储方法,所有存储方法都返回 Future<bool>,表示操作是否成功。

4.2.1 存储 String
dart 复制代码
// 存储字符串
bool success = await prefs.setString('username', '张三');
4.2.2 存储 int
dart 复制代码
// 存储整数
bool success = await prefs.setInt('age', 25);
4.2.3 存储 double
dart 复制代码
// 存储浮点数
bool success = await prefs.setDouble('height', 175.5);
4.2.4 存储 bool
dart 复制代码
// 存储布尔值
bool success = await prefs.setBool('isLoggedIn', true);
4.2.5 存储 List<String>
dart 复制代码
// 存储字符串列表
bool success = await prefs.setStringList('favorites', ['苹果', '香蕉', '橙子']);

4.3 读取数据

读取数据时,如果指定的键不存在,会返回 null 或默认值。

4.3.1 读取 String
dart 复制代码
// 读取字符串,如果不存在返回 null
String? username = prefs.getString('username');

// 提供默认值
String username = prefs.getString('username') ?? '默认用户';
4.3.2 读取 int
dart 复制代码
// 读取整数,如果不存在返回 null
int? age = prefs.getInt('age');

// 提供默认值
int age = prefs.getInt('age') ?? 0;
4.3.3 读取 double
dart 复制代码
// 读取浮点数,如果不存在返回 null
double? height = prefs.getDouble('height');

// 提供默认值
double height = prefs.getDouble('height') ?? 0.0;
4.3.4 读取 bool
dart 复制代码
// 读取布尔值,如果不存在返回 null
bool? isLoggedIn = prefs.getBool('isLoggedIn');

// 提供默认值
bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
4.3.5 读取 List<String>
dart 复制代码
// 读取字符串列表,如果不存在返回 null
List<String>? favorites = prefs.getStringList('favorites');

// 提供默认值
List<String> favorites = prefs.getStringList('favorites') ?? [];

五、常用 API 详解

5.1 remove - 删除指定键

删除指定键及其对应的值:

dart 复制代码
// 删除指定键
bool success = await prefs.remove('username');

返回值说明:

  • true:删除成功
  • false:删除失败

5.2 clear - 清空所有数据

清空 SharedPreferences 中的所有数据:

dart 复制代码
// 清空所有数据
bool success = await prefs.clear();

使用场景:

  • 用户退出登录时清除缓存数据
  • 应用重置功能
  • 调试时清除测试数据

⚠️ 警告clear() 会删除所有数据,请谨慎使用!

5.3 containsKey - 检查键是否存在

检查指定的键是否存在于 SharedPreferences 中:

dart 复制代码
// 检查键是否存在
bool exists = prefs.containsKey('username');

if (exists) {
  print('键 username 存在');
} else {
  print('键 username 不存在');
}

5.4 getKeys - 获取所有键

获取 SharedPreferences 中存储的所有键:

dart 复制代码
// 获取所有键
Set<String> allKeys = prefs.getKeys();

// 遍历所有键
for (String key in allKeys) {
  print('键: $key');
}

5.5 reload - 重新加载数据

从磁盘重新加载 SharedPreferences 数据,用于同步其他进程或线程对数据的修改:

dart 复制代码
// 重新加载数据
bool success = await prefs.reload();

使用场景:

  • 多进程/多线程环境下需要同步数据时
  • 确保获取到最新的数据

六、完整示例代码

下面是一个完整的示例应用,展示了 shared_preferences 的各种用法:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'SharedPreferences 示例',
      home: SharedPreferencesDemo(),
    );
  }
}

class SharedPreferencesDemo extends StatefulWidget {
  const SharedPreferencesDemo({super.key});

  @override
  State<SharedPreferencesDemo> createState() => _SharedPreferencesDemoState();
}

class _SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
  final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  late Future<int> _counter;
  late Future<String> _username;
  late Future<bool> _isDarkMode;
  late Future<double> _fontSize;
  late Future<List<String>> _favorites;

  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _fontSizeController = TextEditingController();
  final TextEditingController _favoriteController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _counter = _prefs.then((SharedPreferences prefs) {
      return prefs.getInt('counter') ?? 0;
    });
    _username = _prefs.then((SharedPreferences prefs) {
      return prefs.getString('username') ?? '';
    });
    _isDarkMode = _prefs.then((SharedPreferences prefs) {
      return prefs.getBool('isDarkMode') ?? false;
    });
    _fontSize = _prefs.then((SharedPreferences prefs) {
      return prefs.getDouble('fontSize') ?? 14.0;
    });
    _favorites = _prefs.then((SharedPreferences prefs) {
      return prefs.getStringList('favorites') ?? [];
    });
  }

  Future<void> _incrementCounter() async {
    final SharedPreferences prefs = await _prefs;
    final int counter = (prefs.getInt('counter') ?? 0) + 1;
    setState(() {
      _counter = prefs.setInt('counter', counter).then((bool success) {
        return counter;
      });
    });
  }

  Future<void> _resetCounter() async {
    final SharedPreferences prefs = await _prefs;
    setState(() {
      _counter = prefs.setInt('counter', 0).then((bool success) {
        return 0;
      });
    });
  }

  Future<void> _saveUsername() async {
    final SharedPreferences prefs = await _prefs;
    String username = _usernameController.text.trim();
    if (username.isNotEmpty) {
      setState(() {
        _username = prefs.setString('username', username).then((bool success) {
          return username;
        });
      });
      _usernameController.clear();
    }
  }

  Future<void> _toggleDarkMode(bool value) async {
    final SharedPreferences prefs = await _prefs;
    setState(() {
      _isDarkMode = prefs.setBool('isDarkMode', value).then((bool success) {
        return value;
      });
    });
  }

  Future<void> _saveFontSize() async {
    final SharedPreferences prefs = await _prefs;
    double? fontSize = double.tryParse(_fontSizeController.text);
    if (fontSize != null && fontSize > 0) {
      setState(() {
        _fontSize = prefs.setDouble('fontSize', fontSize).then((bool success) {
          return fontSize;
        });
      });
      _fontSizeController.clear();
    }
  }

  Future<void> _addFavorite() async {
    final SharedPreferences prefs = await _prefs;
    String favorite = _favoriteController.text.trim();
    List<String> currentFavorites = await _favorites;
    if (favorite.isNotEmpty && !currentFavorites.contains(favorite)) {
      currentFavorites.add(favorite);
      setState(() {
        _favorites = prefs.setStringList('favorites', currentFavorites).then((bool success) {
          return currentFavorites;
        });
      });
      _favoriteController.clear();
    }
  }

  Future<void> _removeFavorite(String favorite) async {
    final SharedPreferences prefs = await _prefs;
    List<String> currentFavorites = await _favorites;
    currentFavorites.remove(favorite);
    setState(() {
      _favorites = prefs.setStringList('favorites', currentFavorites).then((bool success) {
        return currentFavorites;
      });
    });
  }

  Future<void> _clearAll() async {
    final SharedPreferences prefs = await _prefs;
    bool confirm = await _showConfirmDialog('确定要清空所有数据吗?');
    if (confirm) {
      await prefs.clear();
      setState(() {
        _counter = Future<int>.value(0);
        _username = Future<String>.value('');
        _isDarkMode = Future<bool>.value(false);
        _fontSize = Future<double>.value(14.0);
        _favorites = Future<List<String>>.value([]);
      });
    }
  }

  Future<bool> _showConfirmDialog(String message) async {
    return await showDialog<bool>(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              title: const Text('确认'),
              content: Text(message),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(context).pop(false),
                  child: const Text('取消'),
                ),
                TextButton(
                  onPressed: () => Navigator.of(context).pop(true),
                  child: const Text('确定'),
                ),
              ],
            );
          },
        ) ??
        false;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SharedPreferences 示例'),
        actions: [
          IconButton(
            icon: const Icon(Icons.delete_outline),
            onPressed: _clearAll,
            tooltip: '清空所有数据',
          ),
        ],
      ),
      body: FutureBuilder(
        future: Future.wait([_counter, _username, _isDarkMode, _fontSize, _favorites]),
        builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }

          final int counter = snapshot.data![0] as int;
          final String username = snapshot.data![1] as String;
          final bool isDarkMode = snapshot.data![2] as bool;
          final double fontSize = snapshot.data![3] as double;
          final List<String> favorites = snapshot.data![4] as List<String>;

          return Container(
            decoration: BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [
                  Colors.purple.shade50,
                  Colors.blue.shade50,
                ],
              ),
            ),
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _buildSectionCard(
                    title: 'String 存储 - 用户名',
                    icon: Icons.person,
                    color: Colors.blue,
                    child: Column(
                      children: [
                        Text(
                          '当前用户名: $username',
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                        const SizedBox(height: 12),
                        Row(
                          children: [
                            Expanded(
                              child: TextField(
                                controller: _usernameController,
                                decoration: const InputDecoration(
                                  labelText: '输入用户名',
                                  border: OutlineInputBorder(),
                                  contentPadding: EdgeInsets.symmetric(
                                    horizontal: 12,
                                    vertical: 8,
                                  ),
                                ),
                              ),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: _saveUsername,
                              child: const Text('保存'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),

                  const SizedBox(height: 16),

                  _buildSectionCard(
                    title: 'int 存储 - 计数器',
                    icon: Icons.add_circle_outline,
                    color: Colors.green,
                    child: Column(
                      children: [
                        Text(
                          '计数器值: $counter',
                          style: const TextStyle(
                            fontSize: 24,
                            fontWeight: FontWeight.bold,
                            color: Colors.green,
                          ),
                        ),
                        const SizedBox(height: 12),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            ElevatedButton.icon(
                              onPressed: _incrementCounter,
                              icon: const Icon(Icons.add),
                              label: const Text('增加'),
                            ),
                            ElevatedButton.icon(
                              onPressed: _resetCounter,
                              icon: const Icon(Icons.refresh),
                              label: const Text('重置'),
                              style: ElevatedButton.styleFrom(
                                backgroundColor: Colors.orange,
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),

                  const SizedBox(height: 16),

                  _buildSectionCard(
                    title: 'bool 存储 - 深色模式',
                    icon: Icons.dark_mode,
                    color: Colors.purple,
                    child: SwitchListTile(
                      title: const Text('深色模式'),
                      subtitle: Text(isDarkMode ? '已开启' : '已关闭'),
                      value: isDarkMode,
                      onChanged: _toggleDarkMode,
                    ),
                  ),

                  const SizedBox(height: 16),

                  _buildSectionCard(
                    title: 'double 存储 - 字体大小',
                    icon: Icons.format_size,
                    color: Colors.orange,
                    child: Column(
                      children: [
                        Text(
                          '当前字体大小: ${fontSize.toStringAsFixed(1)}',
                          style: TextStyle(
                            fontSize: fontSize,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                        const SizedBox(height: 12),
                        Row(
                          children: [
                            Expanded(
                              child: TextField(
                                controller: _fontSizeController,
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(
                                  labelText: '输入字体大小 (10-30)',
                                  border: OutlineInputBorder(),
                                  contentPadding: EdgeInsets.symmetric(
                                    horizontal: 12,
                                    vertical: 8,
                                  ),
                                ),
                              ),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: _saveFontSize,
                              child: const Text('保存'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),

                  const SizedBox(height: 16),

                  _buildSectionCard(
                    title: 'List<String> 存储 - 收藏',
                    icon: Icons.favorite,
                    color: Colors.red,
                    child: Column(
                      children: [
                        if (favorites.isEmpty)
                          const Text(
                            '暂无收藏',
                            style: TextStyle(color: Colors.grey),
                          )
                        else
                          Wrap(
                            spacing: 8,
                            runSpacing: 8,
                            children: favorites.map((favorite) {
                              return Chip(
                                label: Text(favorite),
                                onDeleted: () => _removeFavorite(favorite),
                                deleteIconColor: Colors.red,
                              );
                            }).toList(),
                          ),
                        const SizedBox(height: 12),
                        Row(
                          children: [
                            Expanded(
                              child: TextField(
                                controller: _favoriteController,
                                decoration: const InputDecoration(
                                  labelText: '添加收藏',
                                  border: OutlineInputBorder(),
                                  contentPadding: EdgeInsets.symmetric(
                                    horizontal: 12,
                                    vertical: 8,
                                  ),
                                ),
                              ),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: _addFavorite,
                              child: const Text('添加'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),

                  const SizedBox(height: 32),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  Widget _buildSectionCard({
    required String title,
    required IconData icon,
    required Color color,
    required Widget child,
  }) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: color.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    icon,
                    color: color,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Text(
                  title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}

七、常见问题与最佳实践

7.1 常见问题

Q1: 为什么数据没有保存成功?

A: 检查以下几点:

  • 确认已调用 await 等待异步操作完成
  • 确认使用的键名正确(注意大小写)
  • 确认数据类型匹配,不要用 setInt 存储 double 类型的值
Q2: 应用重启后数据丢失了怎么办?

A: shared_preferences 的数据是持久化的,正常情况下不会丢失。如果出现数据丢失:

  • 检查是否调用了 clear() 方法
  • 检查应用是否有清除数据的逻辑
  • 在某些调试模式下,重新安装应用会清除数据
Q3: 可以存储复杂对象吗?

A: shared_preferences 不支持直接存储复杂对象。如果需要存储复杂对象,可以:

  • 将对象转换为 JSON 字符串后存储
  • 使用 jsonEncode()jsonDecode() 进行序列化和反序列化
dart 复制代码
// 存储对象
import 'dart:convert';

class User {
  final String name;
  final int age;

  User({required this.name, required this.age});

  Map<String, dynamic> toJson() => {'name': name, 'age': age};
  factory User.fromJson(Map<String, dynamic> json) =>
      User(name: json['name'], age: json['age']);
}

// 存储
User user = User(name: '张三', age: 25);
await prefs.setString('user', jsonEncode(user.toJson()));

// 读取
String? userJson = prefs.getString('user');
if (userJson != null) {
  User user = User.fromJson(jsonDecode(userJson));
}
Q4: SharedPreferences 的存储容量有限制吗?

A: 理论上没有严格限制,但建议只存储少量轻量级数据。如果需要存储大量数据,建议使用文件存储或数据库。

7.2 最佳实践

1. 使用常量定义键名

为了避免拼写错误和提高代码可维护性,建议使用常量定义键名:

dart 复制代码
class PreferenceKeys {
  static const String username = 'username';
  static const String counter = 'counter';
  static const String isDarkMode = 'isDarkMode';
  static const String fontSize = 'fontSize';
  static const String favorites = 'favorites';
}

// 使用
await prefs.setString(PreferenceKeys.username, '张三');
String? username = prefs.getString(PreferenceKeys.username);
2. 处理异步操作

所有 SharedPreferences 的操作都是异步的,确保使用 await 等待操作完成:

dart 复制代码
// ❌ 错误示例
prefs.setString('key', 'value');
print(prefs.getString('key')); // 可能还没有保存成功

// ✅ 正确示例
await prefs.setString('key', 'value');
print(prefs.getString('key')); // 保存完成后才读取
3. 提供默认值

读取数据时始终提供合理的默认值,避免 null 导致的问题:

dart 复制代码
// ❌ 可能返回 null
String username = prefs.getString('username')!;

// ✅ 提供默认值
String username = prefs.getString('username') ?? '默认用户';
4. 实例缓存与空值检查

为了避免重复获取 SharedPreferences 实例和处理异步加载期间的空值问题,建议:

dart 复制代码
class _MyWidgetState extends State<MyWidget> {
  SharedPreferences? _prefs;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    _prefs = await SharedPreferences.getInstance();
    if (_prefs != null) {
      setState(() {
        // 加载数据
      });
    }
  }

  Future<void> _saveData() async {
    if (_prefs != null) {
      await _prefs!.setString('key', 'value');
    }
  }
}

八、总结

恭喜你!通过这篇文章的学习,你已经掌握了 Flutter 中 shared_preferences 轻量级存储的全面知识。

🎯 核心要点

  1. 基础用法 :使用 getInstance() 获取实例,通过 setXxx()getXxx() 方法进行数据读写
  2. 数据类型 :支持 String、int、double、bool、List<String> 五种数据类型
  3. 异步操作 :所有操作都是异步的,必须使用 await 等待完成
  4. 持久化:数据会持久保存,应用重启后仍然可用
  5. 最佳实践:使用常量定义键名、提供默认值、添加错误处理

📚 使用场景建议

场景 推荐方案
用户设置(主题、语言) shared_preferences
登录状态 shared_preferences
简单的计数器 shared_preferences
收藏列表(少量) shared_preferences
大量数据存储 文件存储 / 数据库
复杂对象存储 JSON 序列化 + shared_preferences
需要加密的数据 加密 + shared_preferences

🚀 进阶方向

掌握了 shared_preferences 后,你还可以探索以下方向:

  1. 文件存储 :学习使用 path_provider + File 进行文件存储
  2. 数据库:学习使用 SQLite、Hive 等数据库方案
  3. 加密存储 :学习使用 flutter_secure_storage 进行加密存储
  4. 状态管理:结合 Provider、Riverpod 等状态管理方案使用
  5. 数据同步:实现本地存储与服务器数据的同步

相关推荐
早點睡3903 小时前
基础入门 Flutter for OpenHarmony:DropdownButton 下拉按钮组件详解
flutter·harmonyos
加农炮手Jinx3 小时前
Flutter for OpenHarmony 实战:network_info_plus 网络扫描与隐私合规深度适配
网络·flutter·华为·harmonyos·鸿蒙
早點睡3903 小时前
基础入门 Flutter for OpenHarmony:AlertDialog 对话框组件详解
flutter·harmonyos
哈__3 小时前
基础入门 Flutter for OpenHarmony:image_picker 图片选择详解
flutter
早點睡3904 小时前
基础入门 Flutter for OpenHarmony:FloatingActionButton 浮动按钮详解
flutter·harmonyos
哈__4 小时前
基础入门 Flutter for OpenHarmony:file_selector 文件选择详解
flutter
左手厨刀右手茼蒿4 小时前
Flutter for OpenHarmony 实战:DartX — 极致简练的开发超能力集
android·flutter·ui·华为·harmonyos
空白诗4 小时前
基础入门 Flutter for OpenHarmony:TabBar 标签栏组件详解
flutter·harmonyos
早點睡3904 小时前
基础入门 Flutter for OpenHarmony:RefreshIndicator 下拉刷新详解
flutter·harmonyos