Flutter框架跨平台鸿蒙开发——食物采购清单APP的开发流程

🚀运行效果展示


Flutter框架跨平台鸿蒙开发------食材采购清单APP的开发流程

前言

在快节奏的现代生活中,合理规划食材采购成为了许多人关注的焦点。为了帮助用户更高效地管理食材采购,我们开发了一款基于Flutter框架的跨平台食材采购清单APP。该APP不仅支持在鸿蒙系统上运行,还能在Android、iOS等平台上使用,实现了真正的跨平台开发。本文将详细介绍该APP的开发流程,包括核心功能实现、代码展示以及技术架构等内容。

应用介绍

食材采购清单APP是一款专为家庭用户和烹饪爱好者设计的工具型应用,主要功能包括:

  • 食材管理:用户可以手动添加食材到购物清单,设置数量和单位
  • 批量添加:支持从菜谱中一键添加所有食材到购物清单
  • 采购状态管理:标记已购买的食材,方便用户在购物时快速识别
  • 清单管理:支持清空已购买的食材或清空所有食材
  • 数据持久化:使用本地存储保存购物清单数据,确保数据不会丢失

核心功能实现及代码展示

1. 技术架构

本项目采用了以下技术架构:
用户界面
业务逻辑层
数据存储层
本地存储
模型层

2. 数据模型设计

首先,我们需要设计购物清单的数据模型。创建了ShoppingItem类来表示单个采购项:

dart 复制代码
/// 采购清单项模型类
class ShoppingItem {
  /// 采购项ID
  final String id;

  /// 食材名称
  final String name;

  /// 数量
  final double quantity;

  /// 单位
  final String unit;

  /// 是否已购买
  bool isPurchased;

  /// 所属菜谱(可选)
  final String? recipeId;

  /// 构造函数
  ShoppingItem({
    required this.id,
    required this.name,
    required this.quantity,
    required this.unit,
    this.isPurchased = false,
    this.recipeId,
  });

  /// 从映射创建采购项对象
  factory ShoppingItem.fromMap(Map<String, dynamic> map) {
    return ShoppingItem(
      id: map['id'] as String,
      name: map['name'] as String,
      quantity: map['quantity'] as double,
      unit: map['unit'] as String,
      isPurchased: map['isPurchased'] as bool,
      recipeId: map['recipeId'] as String?,
    );
  }

  /// 转换为映射
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'quantity': quantity,
      'unit': unit,
      'isPurchased': isPurchased,
      'recipeId': recipeId,
    };
  }

  /// 复制采购项对象
  ShoppingItem copyWith({
    String? id,
    String? name,
    double? quantity,
    String? unit,
    bool? isPurchased,
    String? recipeId,
  }) {
    return ShoppingItem(
      id: id ?? this.id,
      name: name ?? this.name,
      quantity: quantity ?? this.quantity,
      unit: unit ?? this.unit,
      isPurchased: isPurchased ?? this.isPurchased,
      recipeId: recipeId ?? this.recipeId,
    );
  }
}

3. 购物清单服务

为了管理购物清单数据,我们创建了ShoppingListService类,负责数据的增删改查操作:

dart 复制代码
import 'dart:convert';
import 'package:flutter_shili/models/shopping_list_model.dart';
import 'package:shared_preferences/shared_preferences.dart';

/// 购物清单服务类
class ShoppingListService {
  /// 存储键
  static const String _shoppingListKey = 'shopping_list';

  /// 获取购物清单
  Future<List<ShoppingItem>> getShoppingList() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_shoppingListKey);

    if (jsonString == null) {
      return [];
    }

    final List<dynamic> jsonList = json.decode(jsonString);
    return jsonList.map((item) => ShoppingItem.fromMap(item)).toList();
  }

  /// 保存购物清单
  Future<void> saveShoppingList(List<ShoppingItem> items) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = items.map((item) => item.toMap()).toList();
    final jsonString = json.encode(jsonList);
    await prefs.setString(_shoppingListKey, jsonString);
  }

  /// 添加购物项
  Future<void> addShoppingItem(ShoppingItem item) async {
    final items = await getShoppingList();
    items.add(item);
    await saveShoppingList(items);
  }

  /// 更新购物项
  Future<void> updateShoppingItem(ShoppingItem updatedItem) async {
    final items = await getShoppingList();
    final index = items.indexWhere((item) => item.id == updatedItem.id);

    if (index != -1) {
      items[index] = updatedItem;
      await saveShoppingList(items);
    }
  }

  /// 删除购物项
  Future<void> deleteShoppingItem(String itemId) async {
    final items = await getShoppingList();
    items.removeWhere((item) => item.id == itemId);
    await saveShoppingList(items);
  }

  /// 切换购物项购买状态
  Future<void> togglePurchasedStatus(String itemId) async {
    final items = await getShoppingList();
    final index = items.indexWhere((item) => item.id == itemId);

    if (index != -1) {
      items[index] = items[index].copyWith(isPurchased: !items[index].isPurchased);
      await saveShoppingList(items);
    }
  }

  /// 从菜谱添加食材到购物清单
  Future<void> addIngredientsFromRecipe(
    String recipeId,
    List<String> ingredients,
  ) async {
    final items = await getShoppingList();

    // 解析食材字符串,格式如:"100g 面粉"
    for (final ingredientStr in ingredients) {
      final parts = ingredientStr.split(' ');
      if (parts.length >= 2) {
        // 尝试提取数量和单位
        double quantity = 1.0;
        String unit = '';
        String name = '';

        // 检查第一个部分是否为数字
        final quantityMatch = RegExp(r'^\d+(\.\d+)?').firstMatch(parts[0]);
        if (quantityMatch != null) {
          quantity = double.parse(quantityMatch.group(0)!);
          // 提取单位(如果有)
          unit = parts[0].substring(quantityMatch.end);
          // 剩余部分作为食材名称
          name = parts.sublist(1).join(' ');
        } else {
          // 如果没有数字,整个字符串作为食材名称
          name = ingredientStr;
        }

        // 检查是否已存在相同食材
        final existingIndex = items.indexWhere(
          (item) => item.name == name && item.recipeId == recipeId,
        );
        
        if (existingIndex == -1) {
          // 添加新的购物项
          final newItem = ShoppingItem(
            id: DateTime.now().millisecondsSinceEpoch.toString(),
            name: name,
            quantity: quantity,
            unit: unit,
            recipeId: recipeId,
          );
          items.add(newItem);
        }
      }
    }
    
    await saveShoppingList(items);
  }
  
  /// 清空已购买的物品
  Future<void> clearPurchasedItems() async {
    final items = await getShoppingList();
    final remainingItems = items.where((item) => !item.isPurchased).toList();
    await saveShoppingList(remainingItems);
  }
  
  /// 清空所有购物项
  Future<void> clearAllItems() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_shoppingListKey);
  }
}

4. 购物清单页面

购物清单页面是应用的核心界面,负责展示和管理购物项:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_shili/models/shopping_list_model.dart';
import 'package:flutter_shili/services/shopping_list_service.dart';

/// 购物清单页面
class ShoppingListPage extends StatefulWidget {
  /// 构造函数
  const ShoppingListPage({super.key});
  
  @override
  State<ShoppingListPage> createState() => _ShoppingListPageState();
}

class _ShoppingListPageState extends State<ShoppingListPage> {
  /// 购物清单服务
  final ShoppingListService _service = ShoppingListService();
  
  /// 购物项列表
  List<ShoppingItem> _items = [];
  
  /// 是否加载中
  bool _isLoading = true;
  
  /// 初始化状态
  @override
  void initState() {
    super.initState();
    _loadShoppingList();
  }
  
  /// 加载购物清单
  Future<void> _loadShoppingList() async {
    try {
      setState(() {
        _isLoading = true;
      });
      
      final items = await _service.getShoppingList();
      setState(() {
        _items = items;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('加载购物清单失败: $e')),
      );
    }
  }
  
  /// 切换购买状态
  Future<void> _togglePurchased(ShoppingItem item) async {
    try {
      final updatedItem = item.copyWith(isPurchased: !item.isPurchased);
      await _service.updateShoppingItem(updatedItem);
      await _loadShoppingList();
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('更新失败: $e')),
      );
    }
  }
  
  /// 删除购物项
  Future<void> _deleteItem(String itemId) async {
    try {
      await _service.deleteShoppingItem(itemId);
      await _loadShoppingList();
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('删除成功')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('删除失败: $e')),
      );
    }
  }
  
  /// 清空已购买物品
  Future<void> _clearPurchased() async {
    try {
      await _service.clearPurchasedItems();
      await _loadShoppingList();
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('已清空已购买物品')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('操作失败: $e')),
      );
    }
  }
  
  /// 清空所有物品
  Future<void> _clearAll() async {
    try {
      await _service.clearAllItems();
      await _loadShoppingList();
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('已清空所有物品')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('操作失败: $e')),
      );
    }
  }
  
  /// 添加新购物项
  void _addNewItem() {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => AddShoppingItemPage(
          onAdd: _loadShoppingList,
        ),
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    // 分离已购买和未购买的物品
    final purchasedItems = _items.where((item) => item.isPurchased).toList();
    final unpurchasedItems = _items.where((item) => !item.isPurchased).toList();
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('食材采购清单'),
        actions: [
          if (_items.isNotEmpty) ...[
            IconButton(
              onPressed: _clearPurchased,
              icon: const Icon(Icons.check_circle_outline),
              tooltip: '清空已购买',
            ),
            IconButton(
              onPressed: _clearAll,
              icon: const Icon(Icons.delete_sweep_outlined),
              tooltip: '清空所有',
            ),
          ],
        ],
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _items.isEmpty
              ? const Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(
                        Icons.shopping_basket_outlined,
                        size: 64,
                        color: Colors.grey,
                      ),
                      SizedBox(height: 16),
                      Text('购物清单为空'),
                      SizedBox(height: 8),
                      Text('点击下方按钮添加食材', style: TextStyle(color: Colors.grey)),
                    ],
                  ),
                )
              : ListView(
                  padding: const EdgeInsets.all(16),
                  children: [
                    // 未购买的物品
                    if (unpurchasedItems.isNotEmpty) ...[
                      const Text(
                        '待购买',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      ...unpurchasedItems.map((item) => _buildShoppingItem(item)),
                      const SizedBox(height: 24),
                    ],
                    
                    // 已购买的物品
                    if (purchasedItems.isNotEmpty) ...[
                      const Text(
                        '已购买',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                          color: Colors.grey,
                        ),
                      ),
                      const SizedBox(height: 12),
                      ...purchasedItems.map((item) => _buildShoppingItem(item)),
                    ],
                  ],
                ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addNewItem,
        child: const Icon(Icons.add),
        tooltip: '添加食材',
      ),
    );
  }
  
  /// 构建购物项卡片
  Widget _buildShoppingItem(ShoppingItem item) {
    return Card(
      margin: const EdgeInsets.only(bottom: 8),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            // 复选框
            Checkbox(
              value: item.isPurchased,
              onChanged: (value) => _togglePurchased(item),
            ),
            
            // 食材信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    item.name,
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                      decoration: item.isPurchased
                          ? TextDecoration.lineThrough
                          : TextDecoration.none,
                      color: item.isPurchased ? Colors.grey : null,
                    ),
                  ),
                  Text(
                    '${item.quantity}${item.unit}',
                    style: TextStyle(
                      fontSize: 14,
                      color: item.isPurchased ? Colors.grey : Colors.grey[600],
                    ),
                  ),
                  if (item.recipeId != null) ...[
                    const SizedBox(height: 4),
                    Text(
                      '来自菜谱',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[500],
                      ),
                    ),
                  ],
                ],
              ),
            ),
            
            // 删除按钮
            IconButton(
              onPressed: () => _deleteItem(item.id),
              icon: const Icon(Icons.delete_outline),
              color: Colors.red,
            ),
          ],
        ),
      ),
    );
  }
}

/// 添加购物项页面
class AddShoppingItemPage extends StatefulWidget {
  /// 添加成功回调
  final VoidCallback onAdd;
  
  /// 构造函数
  const AddShoppingItemPage({super.key, required this.onAdd});
  
  @override
  State<AddShoppingItemPage> createState() => _AddShoppingItemPageState();
}

class _AddShoppingItemPageState extends State<AddShoppingItemPage> {
  /// 表单键
  final _formKey = GlobalKey<FormState>();
  
  /// 食材名称控制器
  final _nameController = TextEditingController();
  
  /// 数量控制器
  final _quantityController = TextEditingController(text: '1');
  
  /// 单位控制器
  final _unitController = TextEditingController();
  
  /// 购物清单服务
  final ShoppingListService _service = ShoppingListService();
  
  /// 提交表单
  Future<void> _submitForm() async {
    if (_formKey.currentState!.validate()) {
      try {
        final name = _nameController.text.trim();
        final quantity = double.tryParse(_quantityController.text) ?? 1.0;
        final unit = _unitController.text.trim();
        
        final newItem = ShoppingItem(
          id: DateTime.now().millisecondsSinceEpoch.toString(),
          name: name,
          quantity: quantity,
          unit: unit,
        );
        
        await _service.addShoppingItem(newItem);
        widget.onAdd();
        Navigator.pop(context);
        
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('添加成功')),
        );
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('添加失败: $e')),
        );
      }
    }
  }
  
  @override
  void dispose() {
    _nameController.dispose();
    _quantityController.dispose();
    _unitController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('添加食材'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _nameController,
                decoration: const InputDecoration(
                  labelText: '食材名称',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return '请输入食材名称';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              
              Row(
                children: [
                  Expanded(
                    child: TextFormField(
                      controller: _quantityController,
                      decoration: const InputDecoration(
                        labelText: '数量',
                        border: OutlineInputBorder(),
                      ),
                      keyboardType: TextInputType.number,
                      validator: (value) {
                        if (value == null || value.trim().isEmpty) {
                          return '请输入数量';
                        }
                        final quantity = double.tryParse(value);
                        if (quantity == null || quantity <= 0) {
                          return '请输入有效的数量';
                        }
                        return null;
                      },
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: TextFormField(
                      controller: _unitController,
                      decoration: const InputDecoration(
                        labelText: '单位',
                        border: OutlineInputBorder(),
                        hintText: '如:g、kg、个、勺等',
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 32),
              
              ElevatedButton(
                onPressed: _submitForm,
                style: ElevatedButton.styleFrom(
                  minimumSize: const Size(double.infinity, 50),
                ),
                child: const Text('添加到购物清单'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

5. 菜谱集成

为了提升用户体验,我们还实现了从菜谱中一键添加食材到购物清单的功能。在菜谱详情页面中添加了一个"添加到购物清单"按钮:

dart 复制代码
/// 将食材添加到购物清单
Future<void> _addToShoppingList() async {
  try {
    await _shoppingListService.addIngredientsFromRecipe(
      _recipe.id,
      _recipe.ingredients,
    );
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('食材已添加到购物清单')),
    );
  } catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('添加失败: $e')),
    );
  }
}

// 在食材列表下方添加按钮
ElevatedButton.icon(
  onPressed: _addToShoppingList,
  icon: const Icon(Icons.shopping_basket),
  label: const Text('添加到购物清单'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.green,
    minimumSize: const Size(double.infinity, 48),
  ),
),

6. 应用配置

最后,我们需要配置应用的入口文件,设置应用的主题和路由:

dart 复制代码
/// 应用入口文件
/// 配置应用路由和主题
import 'package:flutter/material.dart';
import 'package:flutter_shili/pages/shopping_list_page.dart';

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

/// 应用主类
class MyApp extends StatelessWidget {
  /// 构造函数
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '食材采购清单',
      theme: ThemeData(
        primarySwatch: Colors.green,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ShoppingListPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

技术架构

架构流程图

本地存储 服务层 用户界面 用户 本地存储 服务层 用户界面 用户 添加食材 调用addShoppingItem() 保存数据 返回保存结果 返回操作结果 显示操作成功 标记食材为已购买 调用togglePurchasedStatus() 更新数据 返回更新结果 返回操作结果 显示更新后的状态 从菜谱添加食材 调用addIngredientsFromRecipe() 批量保存数据 返回保存结果 返回操作结果 显示添加成功

目录结构

复制代码
lib/
├── models/
│   ├── recipe_model.dart        # 菜谱模型
│   └── shopping_list_model.dart # 购物清单模型
├── services/
│   ├── recipe_service.dart       # 菜谱服务
│   └── shopping_list_service.dart # 购物清单服务
├── pages/
│   ├── recipe_detail_page.dart   # 菜谱详情页面
│   ├── shopping_list_page.dart   # 购物清单页面
│   └── ...
└── main.dart                     # 应用入口

核心功能实现

1. 数据持久化

应用使用shared_preferences包实现数据持久化,将购物清单数据存储在本地:

dart 复制代码
// 保存购物清单
Future<void> saveShoppingList(List<ShoppingItem> items) async {
  final prefs = await SharedPreferences.getInstance();
  final jsonList = items.map((item) => item.toMap()).toList();
  final jsonString = json.encode(jsonList);
  await prefs.setString(_shoppingListKey, jsonString);
}

// 获取购物清单
Future<List<ShoppingItem>> getShoppingList() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString(_shoppingListKey);
  
  if (jsonString == null) {
    return [];
  }
  
  final List<dynamic> jsonList = json.decode(jsonString);
  return jsonList.map((item) => ShoppingItem.fromMap(item)).toList();
}

2. 响应式布局

应用采用响应式布局设计,确保在不同屏幕尺寸的设备上都能正常显示:

dart 复制代码
// 使用Expanded和Flexible组件实现响应式布局
Row(
  children: [
    Expanded(
      child: TextFormField(
        // 数量输入框
      ),
    ),
    const SizedBox(width: 16),
    Expanded(
      child: TextFormField(
        // 单位输入框
      ),
    ),
  ],
),

// 使用ListView实现滚动布局
ListView(
  padding: const EdgeInsets.all(16),
  children: [
    // 购物项列表
  ],
),

3. 错误处理

应用实现了完善的错误处理机制,确保在各种情况下都能给用户提供友好的反馈:

dart 复制代码
// 加载购物清单时的错误处理
try {
  // 加载数据
} catch (e) {
  setState(() {
    _isLoading = false;
  });
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('加载购物清单失败: $e')),
  );
}

// 添加购物项时的错误处理
try {
  // 添加数据
} catch (e) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('添加失败: $e')),
  );
}

测试与部署

测试

应用开发完成后,我们进行了以下测试:

  • 功能测试:验证所有核心功能是否正常工作
  • 跨平台测试:在鸿蒙、Android和iOS平台上测试应用
  • 性能测试:测试应用的响应速度和内存使用情况
  • 用户体验测试:邀请用户测试应用,收集反馈

部署

应用部署流程如下:

  1. 代码构建 :使用flutter build命令构建应用
  2. 签名打包:为应用添加数字签名
  3. 平台发布:根据不同平台的要求发布应用

总结

通过本项目的开发,我们成功实现了一款功能完善的食材采购清单APP,该APP具有以下特点:

  1. 跨平台兼容:基于Flutter框架开发,支持在鸿蒙、Android、iOS等平台上运行
  2. 功能完善:支持手动添加食材、从菜谱批量添加食材、标记采购状态、管理购物清单等功能
  3. 用户友好:界面简洁美观,操作流程顺畅,提供了良好的用户体验
  4. 数据安全:使用本地存储保存数据,确保数据不会丢失
  5. 响应式设计:适配不同屏幕尺寸的设备,提供一致的用户体验

本项目展示了Flutter框架在跨平台开发中的强大能力,特别是在鸿蒙系统上的应用。通过合理的架构设计和代码组织,我们实现了一个功能完整、性能良好的跨平台应用。

📚 参考资料

  1. Flutter官方文档
  2. flutter_tts库文档
  3. 鸿蒙开发者文档

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

相关推荐
晚霞的不甘2 小时前
Flutter for OpenHarmony《智慧字典》 App 主页深度优化解析:从视觉动效到交互体验的全面升级
前端·flutter·microsoft·前端框架·交互
2601_949720262 小时前
flutter_for_openharmony手语学习app实战+个人中心实现
学习·flutter
子春一2 小时前
Flutter for OpenHarmony:构建一个智能长度单位转换器,深入解析 Flutter 中的多字段联动、输入同步与工程化表单设计
开发语言·javascript·flutter
2601_949613022 小时前
flutter_for_openharmony家庭药箱管理app实战+用药提醒列表实现
服务器·前端·flutter
翰德恩咨询2 小时前
华为2C营销:爆品打造,用IPMS驱动产品上市即上量
华为·ipms流程
前端不太难3 小时前
HarmonyOS 游戏里,主线程到底该干什么?
游戏·状态模式·harmonyos
kirk_wang3 小时前
Flutter艺术探索-Flutter内存管理:内存泄漏检测与优化
flutter·移动开发·flutter教程·移动开发教程
Miguo94well3 小时前
Flutter框架跨平台鸿蒙开发——演讲稿生成器APP的开发流程
flutter·华为·harmonyos·鸿蒙
雨季6663 小时前
Flutter 三端应用实战:OpenHarmony 简易数字累加器开发指南
开发语言·flutter·ui·ecmascript