Flutter框架跨平台鸿蒙开发——每日日记APP的开发流程
本文介绍了使用Flutter框架开发跨平台每日日记APP的全流程,重点展示了在鸿蒙系统上的实现方案。APP具备日记管理、心情天气标签、本地存储等核心功能,采用分层架构设计,包含数据模型、业务逻辑和界面展示三层。文章详细讲解了数据模型设计、存储服务实现等关键技术点,通过SharedPreferences+内存存储的方案确保数据持久化。该应用兼容鸿蒙、Android和iOS平台,体现了Flutter&
🚀运行效果展示


Flutter框架跨平台鸿蒙开发——每日日记APP的开发流程
📝 前言
随着移动应用开发的快速发展,跨平台开发框架成为了开发者们的首选。Flutter作为Google推出的开源UI工具包,以其高性能、高保真的UI渲染和"一次编写,多处运行"的特性,受到了广泛关注。同时,鸿蒙系统作为华为自主研发的分布式操作系统,也在快速发展,成为移动应用开发的重要平台。
本文将详细介绍如何使用Flutter框架开发一款跨平台的每日日记APP,并成功运行在鸿蒙系统上。我们将从项目架构设计、核心功能实现、鸿蒙平台适配等方面,全面展示Flutter跨平台开发的流程和最佳实践。
📱 应用介绍
功能概述
每日日记APP是一款简洁易用的跨平台日记应用,支持用户记录每日心情、天气和生活点滴。应用采用现代化的UI设计,提供流畅的用户体验,同时支持数据本地存储。
核心功能
- ✍️ 日记管理:添加、编辑、删除日记
- 📅 日期管理:按日期查看和管理日记
- 😊 心情标签:支持选择多种心情标签(开心、平静、悲伤、愤怒等)
- ☀️ 天气标签:支持选择多种天气标签(晴天、多云、雨天、雪天等)
- 💾 本地存储:数据持久化存储,确保数据安全
- 📱 跨平台兼容:同时支持鸿蒙、Android和iOS平台
技术栈
| 技术 | 版本/说明 |
|---|---|
| 框架 | Flutter 3.x |
| 语言 | Dart 3.x |
| 状态管理 | 原生StatefulWidget |
| 存储 | SharedPreferences + 内存存储(降级方案) |
| UI组件 | Material Design |
| 平台支持 | 鸿蒙、Android、iOS |
🏗️ 项目架构设计
分层架构
采用经典的三层架构设计,确保代码的可维护性和可扩展性:
├── models/ # 数据模型层,定义数据结构
├── services/ # 业务逻辑层,处理业务逻辑
├── screens/ # 界面展示层,显示UI和处理用户交互
└── widgets/ # 自定义组件层,复用UI组件
核心类关系图
🚀 核心功能实现
1. 数据模型设计
/// 日记条目模型
/// 用于表示单个日记条目
class DiaryEntry {
/// 日记ID
String id;
/// 日记标题
String title;
/// 日记内容
String content;
/// 日记日期
DateTime date;
/// 日记心情标签
String mood;
/// 日记天气标签
String weather;
/// 构造函数
DiaryEntry({
required this.id,
required this.title,
required this.content,
required this.date,
required this.mood,
required this.weather,
});
/// 将日记条目转换为Map,用于存储
Map<String, dynamic> toMap() {
return {
'id': id,
'title': title,
'content': content,
'date': date.toIso8601String(),
'mood': mood,
'weather': weather,
};
}
/// 从Map创建日记条目,用于读取存储数据
factory DiaryEntry.fromMap(Map<String, dynamic> map) {
return DiaryEntry(
id: map['id'],
title: map['title'],
content: map['content'],
date: DateTime.parse(map['date']),
mood: map['mood'],
weather: map['weather'],
);
}
}
2. 存储服务实现
/// 本地存储服务
class StorageService {
/// 单例实例
static final StorageService _instance = StorageService._internal();
/// SharedPreferences实例
SharedPreferences? _prefs;
/// 内存存储,作为SharedPreferences的备选方案
final Map<String, String> _memoryStorage = {};
/// 构造函数
factory StorageService() => _instance;
/// 内部构造函数
StorageService._internal();
/// 初始化存储服务
Future<void> init() async {
try {
_prefs = await SharedPreferences.getInstance();
debugPrint('SharedPreferences初始化成功');
} catch (e) {
debugPrint('SharedPreferences初始化失败: $e');
_prefs = null;
}
}
/// 从存储中获取字符串值
Future<String?> getString(String key, {String? defaultValue}) async {
try {
// 尝试从SharedPreferences获取
if (_prefs != null) {
try {
final value = _prefs?.getString(key);
if (value != null) {
return value;
}
} catch (e) {
debugPrint('从SharedPreferences获取字符串失败,使用内存存储: $e');
}
}
// 从内存存储获取
return _memoryStorage[key] ?? defaultValue;
} catch (e) {
debugPrint('获取字符串时出错: $e');
return defaultValue;
}
}
/// 保存字符串值到存储
Future<void> setString(String key, String value) async {
try {
// 尝试保存到SharedPreferences
if (_prefs != null) {
try {
await _prefs?.setString(key, value);
debugPrint('保存字符串到SharedPreferences成功: $key');
return;
} catch (e) {
debugPrint('保存字符串到SharedPreferences失败,使用内存存储: $e');
}
}
// 保存到内存存储
_memoryStorage[key] = value;
debugPrint('保存字符串到内存存储成功: $key');
} catch (e) {
debugPrint('保存字符串时出错: $e');
}
}
}
3. 日记服务实现
/// 日记服务类
/// 用于处理日记的存储、读取、更新和删除操作
class DiaryService {
/// 存储服务实例
final StorageService _storageService = StorageService();
/// 日记存储的键名
static const String _diaryEntriesKey = 'diary_entries';
/// 构造函数
DiaryService();
/// 保存日记条目
Future<void> saveDiaryEntry(DiaryEntry entry) async {
try {
// 获取所有日记条目
final entries = await getAllDiaryEntries();
// 查找是否已存在相同ID的日记
final index = entries.indexWhere((e) => e.id == entry.id);
if (index != -1) {
// 更新现有日记
entries[index] = entry;
} else {
// 添加新日记
entries.add(entry);
}
// 保存回存储
final json = jsonEncode(entries.map((e) => e.toMap()).toList());
await _storageService.setString(_diaryEntriesKey, json);
debugPrint('日记保存成功: ${entry.title}');
} catch (e) {
debugPrint('保存日记时出错: $e');
}
}
/// 获取所有日记条目
Future<List<DiaryEntry>> getAllDiaryEntries() async {
try {
// 从存储中获取日记条目
final json = await _storageService.getString(_diaryEntriesKey) ?? '[]';
final List<dynamic> entriesJson = jsonDecode(json);
// 转换为DiaryEntry对象并按日期倒序排序
final entries = entriesJson
.map((e) => DiaryEntry.fromMap(e as Map<String, dynamic>))
.toList();
// 按日期倒序排序
entries.sort((a, b) => b.date.compareTo(a.date));
return entries;
} catch (e) {
debugPrint('获取所有日记时出错: $e');
return [];
}
}
}
4. 日记列表屏幕
/// 日记列表屏幕
class DiaryListScreen extends StatefulWidget {
/// 构造函数
const DiaryListScreen({super.key});
State<DiaryListScreen> createState() => _DiaryListScreenState();
}
class _DiaryListScreenState extends State<DiaryListScreen> {
/// 日记服务实例
final DiaryService _diaryService = DiaryService();
/// 日记列表
List<DiaryEntry> _diaryEntries = [];
/// 是否正在加载数据
bool _isLoading = true;
void initState() {
super.initState();
// 加载日记列表
_loadDiaryEntries();
}
/// 加载日记列表
Future<void> _loadDiaryEntries() async {
setState(() {
_isLoading = true;
});
final entries = await _diaryService.getAllDiaryEntries();
setState(() {
_diaryEntries = entries;
_isLoading = false;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('每日日记'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
// 浮动操作按钮,用于添加新日记
floatingActionButton: FloatingActionButton(
onPressed: () async {
// 导航到编辑日记屏幕,添加新日记
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DiaryEditScreen(),
),
);
// 如果返回true,重新加载日记列表
if (result == true) {
await _loadDiaryEntries();
}
},
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: const Icon(Icons.add),
),
// 日记列表
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _diaryEntries.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.book_outlined,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'还没有日记',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
SizedBox(height: 8),
Text(
'点击右下角按钮添加第一篇日记吧',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: _diaryEntries.length,
itemBuilder: (context, index) {
final entry = _diaryEntries[index];
return _buildDiaryCard(entry);
},
),
);
}
/// 构建日记卡片
Widget _buildDiaryCard(DiaryEntry entry) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8),
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () async {
// 导航到编辑日记屏幕,查看/编辑日记
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DiaryEditScreen(diaryEntry: entry),
),
);
// 如果返回true,重新加载日记列表
if (result == true) {
await _loadDiaryEntries();
}
},
onLongPress: () => _deleteDiaryEntry(entry.id),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 日记标题
Text(
entry.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// 日记内容预览
Text(
entry.content,
style: const TextStyle(fontSize: 14, color: Colors.grey),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
// 日记元数据(日期、心情、天气)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_formatDate(entry.date),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
Row(
children: [
// 心情标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12),
),
child: Text(
entry.mood,
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
),
),
const SizedBox(width: 8),
// 天气标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.yellow.shade100,
borderRadius: BorderRadius.circular(12),
),
child: Text(
entry.weather,
style: const TextStyle(
fontSize: 12,
color: Colors.orange,
),
),
),
],
),
],
),
],
),
),
),
);
}
}
5. 日记编辑屏幕
/// 日记编辑屏幕
class DiaryEditScreen extends StatefulWidget {
/// 可选的日记条目,用于编辑现有日记
final DiaryEntry? diaryEntry;
/// 构造函数
const DiaryEditScreen({super.key, this.diaryEntry});
State<DiaryEditScreen> createState() => _DiaryEditScreenState();
}
class _DiaryEditScreenState extends State<DiaryEditScreen> {
/// 日记服务实例
final DiaryService _diaryService = DiaryService();
/// 表单键,用于表单验证
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
/// 标题控制器
final TextEditingController _titleController = TextEditingController();
/// 内容控制器
final TextEditingController _contentController = TextEditingController();
/// 当前选择的心情
String _selectedMood = '开心';
/// 当前选择的天气
String _selectedWeather = '晴天';
/// 可用的心情选项
final List<String> _moodOptions = [
'开心', '平静', '悲伤', '愤怒', '焦虑', '兴奋', '疲惫', '惊讶'
];
/// 可用的天气选项
final List<String> _weatherOptions = [
'晴天', '多云', '雨天', '雪天', '阴天', '雾天', '大风'
];
void initState() {
super.initState();
// 如果是编辑模式,初始化表单数据
if (widget.diaryEntry != null) {
_titleController.text = widget.diaryEntry!.title;
_contentController.text = widget.diaryEntry!.content;
_selectedMood = widget.diaryEntry!.mood;
_selectedWeather = widget.diaryEntry!.weather;
}
}
void dispose() {
// 释放控制器资源
_titleController.dispose();
_contentController.dispose();
super.dispose();
}
/// 保存日记
Future<void> _saveDiary() async {
// 验证表单
if (!_formKey.currentState!.validate()) {
return;
}
// 创建日记条目
final entry = DiaryEntry(
id: widget.diaryEntry?.id ?? _generateId(),
title: _titleController.text.trim(),
content: _contentController.text.trim(),
date: widget.diaryEntry?.date ?? DateTime.now(),
mood: _selectedMood,
weather: _selectedWeather,
);
// 保存日记
await _diaryService.saveDiaryEntry(entry);
// 返回上一个页面,并传递成功信号
Navigator.pop(context, true);
}
/// 生成唯一ID
String _generateId() {
// 使用时间戳和随机数生成唯一ID
return DateTime.now().millisecondsSinceEpoch.toString() +
Random().nextInt(1000).toString();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.diaryEntry == null ? '添加日记' : '编辑日记'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
actions: [
// 保存按钮
TextButton(
onPressed: _saveDiary,
child: const Text(
'保存',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 日记标题输入
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '日记标题',
hintText: '请输入日记标题',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入日记标题';
}
return null;
},
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 16),
// 日记内容输入
TextFormField(
controller: _contentController,
decoration: const InputDecoration(
labelText: '日记内容',
hintText: '今天发生了什么有趣的事情?',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入日记内容';
}
return null;
},
maxLines: 10,
style: const TextStyle(fontSize: 16),
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 20),
// 心情选择
const Text(
'心情',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _moodOptions.map((mood) {
return ChoiceChip(
label: Text(mood),
selected: _selectedMood == mood,
onSelected: (selected) {
if (selected) {
setState(() {
_selectedMood = mood;
});
}
},
selectedColor: Colors.blue,
labelStyle: TextStyle(
color: _selectedMood == mood ?
Colors.white : Colors.black,
),
);
}).toList(),
),
const SizedBox(height: 20),
// 天气选择
const Text(
'天气',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _weatherOptions.map((weather) {
return ChoiceChip(
label: Text(weather),
selected: _selectedWeather == weather,
onSelected: (selected) {
if (selected) {
setState(() {
_selectedWeather = weather;
});
}
},
selectedColor: Colors.orange,
labelStyle: TextStyle(
color: _selectedWeather == weather ? Colors.white : Colors.black,
),
);
}).toList(),
),
const SizedBox(height: 32),
// 保存按钮
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _saveDiary,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('保存日记'),
),
),
],
),
),
),
);
🛠️ 鸿蒙平台适配
配置调整
-
添加鸿蒙支持:在项目根目录下运行
flutter create --platforms ohos .,添加鸿蒙平台支持 -
修改main.dart:更新入口文件,初始化存储服务
/// 日记APP主入口
void main() async {
// 确保Flutter初始化完成
WidgetsFlutterBinding.ensureInitialized();
// 初始化存储服务
final storageService = StorageService();
await storageService.init();
runApp(const DiaryApp());
}
平台兼容性处理
在鸿蒙平台上,部分Flutter插件可能存在兼容性问题,如SharedPreferences。为了解决这个问题,我们实现了以下方案:
-
双层存储机制:
- 优先使用SharedPreferences进行持久化存储
- 当SharedPreferences不可用时,自动切换到内存存储
- 确保应用在任何情况下都能正常运行
-
优雅的错误处理:
- 捕获并记录平台特定的异常
- 提供友好的用户反馈
- 确保应用不会崩溃
-
条件编译优化:
- 针对不同平台使用不同的实现
- 优化性能和资源占用
适配流程图
🚀 项目构建与运行
构建流程
运行命令
# 运行应用
flutter run
# 仅构建HAP包
flutter build ohos
# 构建并安装HAP包
flutter install
调试工具
- Flutter DevTools:用于调试UI、性能分析和内存监控
- 鸿蒙开发者工具:用于鸿蒙平台特定的调试和分析
- Dart Observatory:用于Dart VM的调试和性能分析
📊 性能优化
内存优化
- 合理使用控制器:及时释放TextEditingController等资源
- 避免内存泄漏:正确管理Stream和Subscription
- 优化列表渲染:使用ListView.builder进行懒加载
- 减少不必要的重建:合理使用const和final关键字
性能监控
- 使用Flutter DevTools:监控应用性能指标
- 添加性能日志:记录关键操作的执行时间
- 分析渲染性能:使用Performance Overlay查看渲染帧率
- 优化布局层次:减少Widget树的深度
🎯 总结
技术经验
-
分层架构设计:采用分层架构,实现了UI层、业务层和数据层的解耦,提高了代码的可维护性和可扩展性
-
平台兼容性处理:针对鸿蒙平台的特性,实现了双层存储机制和优雅的错误处理,确保应用在不同平台上都能正常运行
-
性能优化:通过合理使用控制器、优化列表渲染等方式,提高了应用的性能和响应速度
-
用户体验设计:采用现代化的UI设计和流畅的交互体验,提高了用户的使用满意度
未来展望
🔮 功能扩展:
- 添加云同步功能,实现多设备数据同步
- 支持图片和视频附件,丰富日记内容
- 添加日记分类和搜索功能,方便用户管理
- 支持日记导出和导入,增强数据安全性
- 添加主题切换功能,满足个性化需求
📝 结语
通过本项目的开发,我们成功实现了一款基于Flutter框架的跨平台日记APP,并在鸿蒙系统上运行。Flutter的跨平台特性使得我们能够用一套代码同时支持多个平台,大大提高了开发效率。同时,我们也遇到了一些平台特定的挑战,但通过合理的设计和优化,成功解决了这些问题。
随着Flutter框架的不断发展和鸿蒙系统的日益成熟,跨平台开发将变得更加便捷和高效。我们期待未来能够看到更多基于Flutter的优秀跨平台应用出现在鸿蒙生态中。
💡 如果你对本项目有任何疑问或建议,欢迎留言交流!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)