Flutter for OpenHarmony 美食烹饪助手 App 实战:项目初始化与架构设计
本文介绍了开发一款跨平台美食烹饪助手应用的技术选型和架构设计。采用Flutter框架实现跨平台开发,结合GetX状态管理、flutter_screenutil屏幕适配等技术。应用分为首页、菜谱库、厨房工具和个人中心四大模块,采用模块化设计便于协作维护。详细说明了应用入口文件的配置、第三方依赖包的引入(包括鸿蒙系统适配包),并设计了核心的Recipe数据模型,包含菜品基本信息、食材步骤、营养成分等属

在移动应用开发领域,一个好的开始往往决定了项目的成败。今天我们要开发一款美食烹饪助手应用,它不仅能帮助用户发现和学习各种菜谱,还能提供实用的厨房工具。在动手写代码之前,我们需要先做好项目的整体规划和架构设计。
为什么选择这些技术栈
开发一款跨平台应用,技术选型至关重要。我选择 Flutter 作为开发框架,主要是看中它的跨平台能力和丰富的组件生态。对于状态管理,GetX 是个不错的选择,它不仅轻量级,而且学习曲线平缓,非常适合中小型项目。
在 UI 适配方面,flutter_screenutil 能够很好地解决不同屏幕尺寸的适配问题。我们只需要按照设计稿的尺寸来写代码,它会自动帮我们做好适配工作。这样就不用担心在不同设备上显示效果不一致的问题了。
数据可视化是这个应用的一大亮点。fl_chart 提供了丰富的图表类型,可以用来展示营养成分、学习进度等数据。table_calendar 则能帮我们实现菜单规划功能,让用户可以提前安排每天的饮食。
应用的整体架构
这个应用分为四个主要模块,每个模块都有明确的职责。首页模块负责展示推荐内容和分类导航,菜谱库模块管理用户的菜谱收藏和创作,厨房工具模块提供各种实用工具,个人中心模块则处理用户相关的功能。
这种模块化的设计有很多好处。首先是代码组织清晰,每个模块的文件都放在对应的文件夹里,找起来很方便。其次是便于团队协作,不同的开发者可以负责不同的模块,互不干扰。最后是方便后期维护,如果某个功能需要修改,我们能快速定位到对应的代码。
创建应用入口
让我们从应用的入口文件开始。这个文件是整个应用的起点,负责初始化各种配置。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'pages/cooking_main_page.dart';
void main() {
runApp(const CookingApp());
}
main 函数是 Dart 程序的入口点,这里我们直接调用 runApp 来启动应用。CookingApp 是我们自定义的根组件,它会负责整个应用的配置和初始化工作。
class CookingApp extends StatelessWidget {
const CookingApp({super.key});
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return GetMaterialApp(
title: '美食烹饪助手',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.orange,
scaffoldBackgroundColor: const Color(0xFFF5F5F5),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
elevation: 0,
),
),
home: const CookingMainPage(),
);
},
);
}
}
ScreenUtilInit 是屏幕适配的关键组件。我们设置设计稿尺寸为 375x812,这是 iPhone X 的屏幕尺寸,也是目前比较通用的设计标准。minTextAdapt 设置为 true 可以确保文字在小屏幕上也能正常显示,splitScreenMode 则是为了支持分屏模式。
GetMaterialApp 是 GetX 提供的根组件,它在 MaterialApp 的基础上增加了路由管理和状态管理的功能。我们把 debugShowCheckedModeBanner 设置为 false,这样就不会在右上角显示 debug 标签了。
主题配置采用橙色作为主色调,这个颜色给人温暖、有食欲的感觉,很适合美食类应用。背景色选择浅灰色,可以让内容卡片更加突出。AppBar 统一使用橙色背景和白色文字,并且去掉阴影,这样看起来更加简洁现代。
配置项目依赖
依赖配置是项目初始化的重要环节。我们需要在 pubspec.yaml 文件中声明所有需要用到的第三方包。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
get: ^4.6.5
flutter_screenutil: ^5.9.0
convex_bottom_bar: ^3.0.0
fl_chart: ^0.65.0
intl: ^0.20.2
percent_indicator: ^4.2.3
table_calendar: ^3.0.9
lottie: ^2.3.2
uuid: ^4.2.2
这些是常规的依赖包,可以直接从 pub.dev 获取。get 用于状态管理和路由,flutter_screenutil 负责屏幕适配,convex_bottom_bar 提供漂亮的底部导航栏,fl_chart 用于绘制图表,其他的都是一些辅助工具库。
特别需要注意的是鸿蒙适配的依赖包,它们需要从特定的 git 仓库获取:
path_provider:
git:
url: "https://gitcode.com/openharmony-sig/flutter_packages.git"
path: "packages/path_provider/path_provider"
ref: "master"
sqflite:
git:
url: "https://gitcode.com/openharmony-sig/flutter_sqflite.git"
path: "sqflite"
ref: "br_v2.3.3+1_ohos"
share_plus:
git:
url: "https://gitcode.com/openharmony-sig/flutter_plus_plugins.git"
path: "packages/share_plus/share_plus"
ref: "br_share_plus-v10.1.1_ohos"
这三个包是专门为鸿蒙系统适配的版本。path_provider 用于获取文件路径,sqflite 是本地数据库,share_plus 提供分享功能。使用 git 方式引入可以确保我们获取到最新的鸿蒙适配代码。
配置完依赖后,运行 flutter pub get 命令来下载这些包。如果遇到网络问题,可以考虑配置国内的镜像源。
设计数据模型
数据模型是应用的基础,它定义了我们要处理的数据结构。菜谱模型是整个应用最核心的数据结构。
class Recipe {
final String id;
final String name;
final String category;
final String cuisine;
final String difficulty;
final int cookTime;
final int servings;
final String imageUrl;
final List<String> ingredients;
final List<String> steps;
final String description;
final int calories;
final bool isFavorite;
final DateTime createdAt;
Recipe({
required this.id,
required this.name,
required this.category,
required this.cuisine,
required this.difficulty,
required this.cookTime,
required this.servings,
required this.imageUrl,
required this.ingredients,
required this.steps,
required this.description,
required this.calories,
this.isFavorite = false,
required this.createdAt,
});
}
这个模型包含了菜谱的所有基本信息。id 是唯一标识符,我们会用 uuid 包来生成。name 是菜谱名称,category 表示分类(如主食、汤品等),cuisine 表示菜系(如川菜、粤菜等)。
difficulty 表示难度等级,我们设计了简单、中等、困难三个级别。cookTime 是烹饪时间,单位是分钟。servings 表示份量,通常是几人份。
ingredients 和 steps 分别存储食材列表和烹饪步骤,使用 List 类型可以方便地添加和展示多个条目。calories 存储热量信息,这对关注健康的用户很有用。
isFavorite 标记是否收藏,默认为 false。createdAt 记录创建时间,可以用来排序和筛选。
为了方便数据的序列化和反序列化,我们还需要添加 toJson 和 fromJson 方法:
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'category': category,
'cuisine': cuisine,
'difficulty': difficulty,
'cookTime': cookTime,
'servings': servings,
'imageUrl': imageUrl,
'ingredients': ingredients,
'steps': steps,
'description': description,
'calories': calories,
'isFavorite': isFavorite ? 1 : 0,
'createdAt': createdAt.toIso8601String(),
};
}
factory Recipe.fromJson(Map<String, dynamic> json) {
return Recipe(
id: json['id'],
name: json['name'],
category: json['category'],
cuisine: json['cuisine'],
difficulty: json['difficulty'],
cookTime: json['cookTime'],
servings: json['servings'],
imageUrl: json['imageUrl'],
ingredients: List<String>.from(json['ingredients']),
steps: List<String>.from(json['steps']),
description: json['description'],
calories: json['calories'],
isFavorite: json['isFavorite'] == 1,
createdAt: DateTime.parse(json['createdAt']),
);
}
toJson 方法将对象转换为 Map,这样就可以存储到数据库或者发送到网络。注意 isFavorite 需要转换为整数,因为 SQLite 不支持布尔类型。createdAt 转换为 ISO 8601 格式的字符串,这是一个标准的日期时间格式。
fromJson 是一个工厂构造函数,用于从 Map 创建对象。List.from 可以安全地将动态列表转换为字符串列表。DateTime.parse 则将字符串解析为日期时间对象。
还需要一个 copyWith 方法,用于创建修改了部分属性的新对象:
Recipe copyWith({
String? id,
String? name,
String? category,
String? cuisine,
String? difficulty,
int? cookTime,
int? servings,
String? imageUrl,
List<String>? ingredients,
List<String>? steps,
String? description,
int? calories,
bool? isFavorite,
DateTime? createdAt,
}) {
return Recipe(
id: id ?? this.id,
name: name ?? this.name,
category: category ?? this.category,
cuisine: cuisine ?? this.cuisine,
difficulty: difficulty ?? this.difficulty,
cookTime: cookTime ?? this.cookTime,
servings: servings ?? this.servings,
imageUrl: imageUrl ?? this.imageUrl,
ingredients: ingredients ?? this.ingredients,
steps: steps ?? this.steps,
description: description ?? this.description,
calories: calories ?? this.calories,
isFavorite: isFavorite ?? this.isFavorite,
createdAt: createdAt ?? this.createdAt,
);
}
copyWith 方法在 Flutter 开发中非常常用,特别是在处理不可变对象时。它允许我们只修改需要改变的属性,其他属性保持不变。比如要切换收藏状态,只需要调用 recipe.copyWith(isFavorite: !recipe.isFavorite) 就可以了。
购物清单模型
除了菜谱,我们还需要一个购物清单模型来管理用户的购物需求。
class ShoppingItem {
final String id;
final String name;
final String category;
final double quantity;
final String unit;
final bool isPurchased;
final DateTime createdAt;
ShoppingItem({
required this.id,
required this.name,
required this.category,
required this.quantity,
required this.unit,
this.isPurchased = false,
required this.createdAt,
});
ShoppingItem copyWith({
String? id,
String? name,
String? category,
double? quantity,
String? unit,
bool? isPurchased,
DateTime? createdAt,
}) {
return ShoppingItem(
id: id ?? this.id,
name: name ?? this.name,
category: category ?? this.category,
quantity: quantity ?? this.quantity,
unit: unit ?? this.unit,
isPurchased: isPurchased ?? this.isPurchased,
createdAt: createdAt ?? this.createdAt,
);
}
}
购物清单模型相对简单一些。name 是物品名称,category 是分类(如蔬菜、肉类等),quantity 和 unit 分别表示数量和单位。isPurchased 标记是否已购买,这样用户在超市购物时可以逐个勾选。
项目文件结构
良好的文件组织能让项目更易维护。我们按照功能模块来组织代码:
lib 目录下有几个主要的子目录。models 存放所有的数据模型,pages 存放所有的页面组件,utils 存放工具类和辅助函数。
pages 目录下又按照四个主要模块分为 home、library、kitchen 和 profile 四个子目录。每个子目录包含该模块的所有页面文件。这种组织方式让代码结构一目了然,新加入项目的开发者也能快速找到需要的文件。
屏幕适配策略
屏幕适配是移动应用开发中的一个重要问题。flutter_screenutil 提供了一套简单有效的解决方案。
我们以 375x812 作为设计基准,这是 iPhone X 的屏幕尺寸。在代码中,所有的尺寸都使用 .w、.h、.sp 等扩展方法。比如 width: 100.w 表示宽度为 100 个设计单位,flutter_screenutil 会根据实际屏幕尺寸自动计算出对应的像素值。
字体大小使用 .sp 单位,它会根据系统字体设置进行调整。这样可以确保用户设置了大字体时,应用的文字也会相应变大,提升了可访问性。
圆角半径使用 .r 单位,这样可以保证在不同屏幕上圆角的视觉效果一致。比如 borderRadius: BorderRadius.circular(12.r) 会在所有设备上呈现相同比例的圆角效果。
路由管理方案
GetX 提供了简洁的路由管理方式。我们不需要预先定义所有的路由,可以直接使用 Get.to() 方法进行页面跳转。
比如要跳转到菜谱详情页,只需要写 Get.to(() => RecipeDetailPage(recipeId: id)) 就可以了。这种方式的好处是类型安全,编译器可以检查参数是否正确。
如果需要返回上一页,调用 Get.back() 即可。如果要返回并传递数据,可以使用 Get.back(result: data)。在前一个页面中,通过 await Get.to() 就能接收到返回的数据。
对于一些常用的页面,我们也可以定义命名路由。在 GetMaterialApp 中配置 getPages 参数,然后使用 Get.toNamed(‘/route’) 进行跳转。命名路由的好处是可以统一管理,也方便做一些路由拦截和权限控制。
开发规范约定
为了保证代码质量和团队协作效率,我们需要制定一些开发规范。
文件命名采用小写下划线的方式,比如 cooking_home_page.dart。这是 Dart 官方推荐的命名风格,也是 Flutter 社区的通用做法。
类名使用大驼峰命名法,比如 CookingHomePage。变量名和方法名使用小驼峰命名法,比如 userName、getUserInfo。常量名使用大写下划线,比如 MAX_COUNT、DEFAULT_TIMEOUT。
每个页面组件单独一个文件,文件名和类名保持一致。如果一个页面有多个子组件,可以在同一个文件中定义,但如果子组件会被其他页面复用,就应该提取到单独的文件中。
代码注释要简洁明了,重点说明为什么这样做,而不是做了什么。好的代码应该是自解释的,通过合理的命名和结构就能让人理解其功能。
性能优化考虑
虽然现在还在项目初期,但性能优化的意识要从一开始就建立起来。
列表渲染是移动应用中最常见的性能瓶颈。我们统一使用 ListView.builder 和 GridView.builder,它们只会渲染可见区域的内容,大大减少了内存占用和渲染开销。
图片加载也需要特别注意。对于网络图片,使用 CachedNetworkImage 可以自动缓存,避免重复下载。对于本地图片,合理控制图片大小,不要使用过大的图片资源。
状态管理要避免不必要的重建。使用 GetX 的响应式变量时,只有真正依赖该变量的组件才会重建。在使用 setState 时,要确保只更新必要的部分,不要在根组件调用 setState。
数据持久化方案
应用需要在本地存储一些数据,比如用户创建的菜谱、收藏记录等。我们使用 sqflite 作为本地数据库。
sqflite 是一个轻量级的 SQLite 封装库,提供了简洁的 API。我们会创建几张表来存储不同类型的数据:recipes 表存储菜谱,favorites 表存储收藏关系,history 表存储浏览历史。
数据库操作应该封装在单独的服务类中,比如 RecipeService、FavoriteService 等。这样可以让业务逻辑和数据访问分离,也方便后期切换数据源。
对于一些简单的配置信息,可以使用 shared_preferences 存储。但要注意它只适合存储少量的键值对数据,不适合存储大量或复杂的数据。
下一步的开发计划
项目初始化完成后,我们就可以开始具体功能的开发了。
首先要实现底部导航栏,这是应用的主框架。我们会使用 convex_bottom_bar 来创建一个美观的凸起式导航栏,包含首页、菜谱库、厨房工具和个人中心四个标签。
然后开发首页模块,包括轮播图、快速入口、推荐菜谱等内容。首页是用户进入应用后看到的第一个界面,需要特别注意视觉效果和交互体验。
接下来是菜谱库模块,这是应用的核心功能。用户可以浏览、搜索、收藏菜谱,也可以创建自己的菜谱。这个模块涉及到较多的数据操作,需要仔细设计数据流。
厨房工具模块提供一些实用的小工具,比如计时器、单位换算器等。这些工具相对独立,可以并行开发。
最后是个人中心模块,包括个人信息、成就系统、数据统计等。这个模块会用到一些图表组件,可以展示用户的学习进度和烹饪数据。
总结
项目初始化和架构设计是应用开发的基础工作,虽然看起来不如写具体功能那么有成就感,但它决定了项目的可维护性和可扩展性。
我们选择了 Flutter + GetX 的技术栈,配置了必要的依赖包,设计了数据模型,制定了开发规范。这些准备工作为后续的开发打下了坚实的基础。
在实际开发中,架构设计不是一成不变的,我们需要根据实际情况进行调整和优化。但有了清晰的初始架构,后续的调整也会更加有序。
下一篇文章我们将实现底部导航栏,搭建起应用的主框架。敬请期待!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)