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

一、使用软件

DevEco Studio

鸿蒙官方集成开发环境,提供项目管理、代码编辑、模拟器调试、应用打包等全流程开发能力,是 Flutter-OH 应用开发的核心工具。


Flutter-OH SDK
面向 OpenHarmony 生态定制的 Flutter 跨平台开发工具包,支持 Dart 语言编译、鸿蒙平台适配、UI 组件渲染与应用构建


OpenHarmony 模拟器
DevEco Studio 内置的鸿蒙设备虚拟运行环境,无需物理真机即可完成 APP 界面预览、功能调试与兼容性验证。
 

二、核心内容

  1. 对整个记事本项目进行整体代码梳理与 BUG 修复。
  2. 完善笔记编辑修改功能,支持修改已有笔记内容和分类标签。
  3. 优化页面布局、按钮样式、卡片间距,美化整体 UI 界面。
  4. 完善收藏置顶功能,收藏笔记固定排在列表最上方。
  5. 增加长按删除、弹窗提示,防止误删笔记。
  6. 本地存储持久化最终调试,确保重启数据不丢失。
  7. 模拟器全功能测试,完成项目收尾、整体验收。

三、操作步骤

步骤 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 美化、最终验收的完整过程,为后续鸿蒙应用开发与项目实战积累了扎实经验。

五、模拟器运行测试

  1. 支持新建、编辑、删除笔记,操作流程完整。
  2. 笔记可选择分类,顶部分类筛选功能正常。
  3. 收藏笔记自动置顶,排序逻辑正常。
  4. 删除有弹窗确认,防止误操作。
  5. 重启模拟器所有数据完整保留。
  6. 界面 UI 美化完成,布局整齐、交互流畅。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐