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

一、使用软件

DevEco Studio

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


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


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

二、核心内容

  1. 扩展笔记实体类,新增 tag 分类标签,分为学习、工作、日常三类。
  2. 编辑页面添加下拉选择框,新建笔记可以手动选择所属分类。
  3. 首页顶部添加分类导航按钮栏,支持全部、学习、工作、日常筛选笔记。
  4. 使用原生本地存储,保存笔记分类信息,重启模拟器数据不丢失。
  5. 修复之前分类不显示、筛选没反应的问题,完善分类逻辑。
  6. 保留搜索、新增、卡片布局、笔记查看等原有全部功能。

三、操作步骤(附带完整代码)

步骤 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:修改笔记模型类 lib/models/note_model.dart

新增分类标签字段,完善序列化保存分类数据。

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:新建存储工具类 lib/utils/note_storage.dart

使用原生本地存储,保存笔记和分类信息

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:编辑页面添加下拉分类选择 lib/pages/markdown_edit_page.dart

加入下拉框,可选择学习、工作、日常,并把分类传回首页。

dart

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;
  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:首页主页面 lib/main.dart(已修复分类筛选、顶部按钮、卡片标签)

dart

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: 'Note App',
      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);
    });
  }

  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();
      }
    });
  }

  // 分类对应颜色
  Color _getTagColor(String tag){
    if(tag == "学习") return Colors.blue;
    if(tag == "工作") return Colors.green;
    return Colors.grey;
  }

  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,
          ),
        );
        _showNotes = List.from(_allNotes);
      });
      await _saveNotes();
    }
  }

  @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(),
                    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("暂无笔记"))
          : ListView.builder(
        itemCount: _showNotes.length,
        itemBuilder: (context,index){
          return Card(
            margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                    decoration: BoxDecoration(
                      color: _getTagColor(_showNotes[index].tag),
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: Text(
                      _showNotes[index].tag,
                      style: const TextStyle(color: Colors.white, fontSize: 10),
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    _showNotes[index].content.length > 20
                        ? "${_showNotes[index].content.substring(0,20)}..."
                        : _showNotes[index].content,
                  ),
                ],
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addNote,
        child: const Icon(Icons.add),
      ),
    );
  }
}

四、800 字实训心得

        本次实训为 Flutter 鸿蒙记事本开发第九天,主要完成笔记分类标签与分类筛选功能开发。为了避免第三方库地址失效、依赖拉取报错等问题,本次放弃使用远程第三方库,只采用 Flutter 原生基础依赖进行开发,项目配置简单、运行稳定,不会出现依赖找不到文件的错误。

        首先修改笔记实体模型,新增 tag 分类标签字段,设定学习、工作、日常三种常用分类,并完善 JSON 序列化和反序列化代码,让每条笔记都能独立保存所属分类。随后在笔记编辑页面添加下拉选择控件,新建笔记时可以自由选择分类类型,选择后将分类数据传回首页进行保存。

        在首页顶部新增分类导航按钮栏,设置全部、学习、工作、日常四个选项,编写分类筛选逻辑方法,点击对应按钮即可动态过滤笔记列表,只展示对应分类内容,实现笔记快速分类查找。直观区分笔记类型,界面更加整洁美观。

        开发过程中遇到过分类不显示、筛选无反应的问题,主要是列表没有及时刷新、分类赋值逻辑不完善导致。通过重新优化筛选方法、刷新列表数据,成功修复分类功能 BUG,实现正常切换筛选。项目沿用原生本地存储方式,能够稳定保存笔记内容和分类标签,重启模拟器后数据不会丢失,同时保留搜索、新增笔记、卡片布局等原有全部功能,互不冲突。

        通过本次第九天实训,我掌握了数据模型扩展、下拉选择组件使用、条件筛选逻辑编写、原生本地存储的使用方法,同时学会排查功能失效 BUG,进一步熟悉了 Flutter 页面布局与交互逻辑,为后续项目打包优化打下良好基础。

五、模拟器运行测试

  1. 新建笔记可正常下拉选择学习、工作、日常分类。
  2. 顶部分类按钮可正常切换,精准筛选对应笔记。
  3. 重启模拟器,笔记和分类数据完整保留不丢失。
  4. 搜索功能、卡片布局、新增笔记全部正常兼容。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐