Flutter 鸿蒙应用开发第十天:项目整体优化完善与收尾打包测试
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
一、使用软件
DevEco Studio
鸿蒙官方集成开发环境,提供项目管理、代码编辑、模拟器调试、应用打包等全流程开发能力,是 Flutter-OH 应用开发的核心工具。

Flutter-OH SDK
面向 OpenHarmony 生态定制的 Flutter 跨平台开发工具包,支持 Dart 语言编译、鸿蒙平台适配、UI 组件渲染与应用构建
OpenHarmony 模拟器
DevEco Studio 内置的鸿蒙设备虚拟运行环境,无需物理真机即可完成 APP 界面预览、功能调试与兼容性验证。
二、核心内容
- 对整个记事本项目进行整体代码梳理与 BUG 修复。
- 完善笔记编辑修改功能,支持修改已有笔记内容和分类标签。
- 优化页面布局、按钮样式、卡片间距,美化整体 UI 界面。
- 完善收藏置顶功能,收藏笔记固定排在列表最上方。
- 增加长按删除、弹窗提示,防止误删笔记。
- 本地存储持久化最终调试,确保重启数据不丢失。
- 模拟器全功能测试,完成项目收尾、整体验收。
三、操作步骤
步骤 1:pubspec.yaml 保持基础依赖不变
name: note_app
description: 鸿蒙记事本
publish_to: none
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
flutter pub get
步骤 2:笔记模型 model 不变
class NoteModel {
String content;
String createTime;
String updateTime;
bool isStar;
String tag;
NoteModel({
required this.content,
required this.createTime,
required this.updateTime,
this.isStar = false,
this.tag = "日常",
});
Map<String,dynamic> toJson(){
return {
"content":content,
"createTime":createTime,
"updateTime":updateTime,
"isStar":isStar,
"tag":tag,
};
}
static NoteModel fromJson(Map<String,dynamic> map){
return NoteModel(
content: map["content"],
createTime: map["createTime"],
updateTime: map["updateTime"],
isStar: map["isStar"] ?? false,
tag: map["tag"] ?? "日常",
);
}
}
步骤 3:存储工具类不变
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/note_model.dart';
class NoteStorage {
static const String _key = 'note_list';
static Future<void> saveNotes(List<NoteModel> notes) async {
final prefs = await SharedPreferences.getInstance();
List<String> jsonList = notes.map((e) => jsonEncode(e.toJson())).toList();
await prefs.setStringList(_key, jsonList);
}
static Future<List<NoteModel>> loadNotes() async {
final prefs = await SharedPreferences.getInstance();
List<String>? strList = prefs.getStringList(_key);
if (strList == null) return [];
return strList.map((e) => NoteModel.fromJson(jsonDecode(e))).toList();
}
}
步骤 4:编辑页完善
import 'package:flutter/material.dart';
class MarkdownEditPage extends StatefulWidget {
final String? initialContent;
final String? initialTag;
const MarkdownEditPage({
super.key,
this.initialContent,
this.initialTag,
});
@override
State<MarkdownEditPage> createState() => _MarkdownEditPageState();
}
class _MarkdownEditPageState extends State<MarkdownEditPage> {
late TextEditingController _controller;
late String _selectTag;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialContent ?? "");
_selectTag = widget.initialTag ?? "日常";
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("编辑笔记"),
actions: [
TextButton(
onPressed: (){
Navigator.pop(context,{
"content":_controller.text,
"tag":_selectTag
});
},
child: const Text("保存",style:TextStyle(color:Colors.white)),
)
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButton<String>(
value: _selectTag,
isExpanded: true,
underline: const SizedBox(),
items: const [
DropdownMenuItem(value: "学习", child: Text("学习")),
DropdownMenuItem(value: "工作", child: Text("工作")),
DropdownMenuItem(value: "日常", child: Text("日常")),
],
onChanged: (val){
setState(() {
_selectTag = val!;
});
},
),
),
const SizedBox(height: 20),
Expanded(
child: TextField(
controller: _controller,
maxLines: null,
expands: true,
decoration: const InputDecoration(
hintText: "请输入笔记内容",
border: OutlineInputBorder(),
),
),
),
],
),
),
);
}
}
步骤 5:Day10 最终完整版 main.dart(完善收藏、修改、删除、UI 美化、分类筛选全部可用)
import 'package:flutter/material.dart';
import 'utils/note_storage.dart';
import 'pages/markdown_edit_page.dart';
import 'models/note_model.dart';
void main() {
runApp(const NoteApp());
}
class NoteApp extends StatelessWidget {
const NoteApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '鸿蒙记事本',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<NoteModel> _allNotes = [];
List<NoteModel> _showNotes = [];
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadNotes();
_searchController.addListener(_searchNotes);
}
Future<void> _loadNotes() async {
final list = await NoteStorage.loadNotes();
setState(() {
_allNotes = list;
_showNotes = List.from(_allNotes);
});
_sortNote();
}
Future<void> _saveNotes() async {
await NoteStorage.saveNotes(_allNotes);
}
void _searchNotes() {
String key = _searchController.text.trim();
setState(() {
if (key.isEmpty) {
_showNotes = List.from(_allNotes);
} else {
_showNotes = _allNotes.where((note) => note.content.contains(key)).toList();
}
});
}
// 分类筛选
void _filterTag(String tag){
setState(() {
if(tag == "全部"){
_showNotes = List.from(_allNotes);
}else{
_showNotes = _allNotes.where((item) => item.tag == tag).toList();
}
});
}
// 收藏排序:收藏在上
void _sortNote() {
List<NoteModel> starList = _allNotes.where((e) => e.isStar).toList();
List<NoteModel> normalList = _allNotes.where((e) => !e.isStar).toList();
setState(() {
_allNotes = [...starList, ...normalList];
_showNotes = List.from(_allNotes);
});
}
// 标签颜色
Color _getTagColor(String tag){
if(tag == "学习") return Colors.blue;
if(tag == "工作") return Colors.green;
return Colors.grey;
}
// 切换收藏
void _toggleStar(int index){
int realIdx = _allNotes.indexOf(_showNotes[index]);
setState(() {
_allNotes[realIdx].isStar = !_allNotes[realIdx].isStar;
});
_sortNote();
_saveNotes();
}
// 新建笔记
Future<void> _addNote() async {
final res = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MarkdownEditPage()),
);
if (res != null && res["content"].toString().trim().isNotEmpty) {
String time = DateTime.now().toString().substring(0,16);
setState(() {
_allNotes.add(
NoteModel(
content: res["content"],
tag: res["tag"],
createTime: time,
updateTime: time,
),
);
});
_sortNote();
await _saveNotes();
}
}
// 编辑笔记
Future<void> _editNote(int index) async {
int realIdx = _allNotes.indexOf(_showNotes[index]);
final res = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MarkdownEditPage(
initialContent: _allNotes[realIdx].content,
initialTag: _allNotes[realIdx].tag,
),
),
);
if(res != null){
String newTime = DateTime.now().toString().substring(0,16);
setState(() {
_allNotes[realIdx].content = res["content"];
_allNotes[realIdx].tag = res["tag"];
_allNotes[realIdx].updateTime = newTime;
});
_sortNote();
await _saveNotes();
}
}
// 删除笔记
void _deleteNote(int index){
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("确认删除"),
content: const Text("确定要删除这条笔记吗?"),
actions: [
TextButton(onPressed: ()=>Navigator.pop(context), child: const Text("取消")),
TextButton(
onPressed: (){
Navigator.pop(context);
int realIdx = _allNotes.indexOf(_showNotes[index]);
setState(() {
_allNotes.removeAt(realIdx);
});
_sortNote();
_saveNotes();
},
child: const Text("删除",style:TextStyle(color:Colors.red)),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("鸿蒙记事本 最终版"),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(90),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: "搜索笔记内容",
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8))),
prefixIcon: Icon(Icons.search),
),
),
),
SizedBox(
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(onPressed: ()=>_filterTag("全部"), child: const Text("全部")),
TextButton(onPressed: ()=>_filterTag("学习"), child: const Text("学习")),
TextButton(onPressed: ()=>_filterTag("工作"), child: const Text("工作")),
TextButton(onPressed: ()=>_filterTag("日常"), child: const Text("日常")),
],
),
)
],
),
),
),
body: _showNotes.isEmpty
? const Center(child: Text("暂无笔记,请点击加号新建",style:TextStyle(color:Colors.grey)))
: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _showNotes.length,
itemBuilder: (context,index){
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: _getTagColor(_showNotes[index].tag),
borderRadius: BorderRadius.circular(6),
),
child: Text(
_showNotes[index].tag,
style: const TextStyle(color: Colors.white, fontSize: 11),
),
),
IconButton(
icon: Icon(
_showNotes[index].isStar ? Icons.star : Icons.star_border,
color: _showNotes[index].isStar ? Colors.amber : Colors.grey,
size: 22,
),
onPressed: ()=>_toggleStar(index),
)
],
),
const SizedBox(height: 8),
Text(
_showNotes[index].content.length > 25
? "${_showNotes[index].content.substring(0,25)}..."
: _showNotes[index].content,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: ()=>_editNote(index),
child: const Text("编辑"),
),
TextButton(
onPressed: ()=>_deleteNote(index),
child: const Text("删除",style:TextStyle(color:Colors.red)),
),
],
)
],
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _addNote,
child: const Icon(Icons.add),
),
);
}
}
四、实训心得
本次是 Flutter 鸿蒙记事本开发第十天,主要对项目进行整体收尾、功能完善、UI 美化与全功能测试,完成整个记事本项目的最终验收。本次全程采用原生基础依赖,不使用任何外部第三方库,避免依赖拉取失败、版本冲突等问题,项目结构干净、运行稳定,适配 OpenHarmony 模拟器环境。
首先对前期代码进行整体梳理,修复零散 BUG,完善笔记编辑功能,支持对已存在的笔记修改内容和更换分类标签,实现完整的新增、编辑、删除闭环操作。其次优化页面 UI 布局,调整卡片圆角、阴影、间距,优化搜索框和分类按钮样式,让整体界面更加整洁美观,符合鸿蒙系统设计风格。
完善收藏置顶逻辑,将收藏的笔记自动排序在列表最上方,方便快速查看重要笔记。增加删除弹窗二次确认,避免用户误删笔记,提升使用体验。同时加固本地持久化存储逻辑,反复测试重启模拟器、退出重进等场景,确保所有笔记内容、分类标签、收藏状态都能完整保留,不会丢失数据。
保留并稳定兼容分类筛选、关键词搜索、下拉选择分类、彩色标签标识等前期所有功能,四大分类按钮切换流畅,筛选结果准确,搜索匹配正常。最后在模拟器进行全流程功能测试,新建、编辑、收藏、删除、分类筛选、搜索、重启保留数据全部正常无异常,无闪退、无功能失效问题。
通过第十天的收尾开发,我系统掌握了 Flutter 页面布局、组件复用、数据持久化、列表排序、条件筛选、弹窗交互、项目整体调试优化等完整开发流程,理解了项目从功能开发到 BUG 修复、UI 美化、最终验收的完整过程,为后续鸿蒙应用开发与项目实战积累了扎实经验。
五、模拟器运行测试
- 支持新建、编辑、删除笔记,操作流程完整。
- 笔记可选择分类,顶部分类筛选功能正常。
- 收藏笔记自动置顶,排序逻辑正常。
- 删除有弹窗确认,防止误操作。
- 重启模拟器所有数据完整保留。
- 界面 UI 美化完成,布局整齐、交互流畅。

更多推荐

所有评论(0)