Flutter实战:开源鸿蒙简易记事本组件

Flutter 三方库 cached_network_image 的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

本文详细介绍如何在Flutter鸿蒙应用中实现一个功能完善的简易记事本,支持笔记的创建、编辑、删除和时间戳记录。

一、前言

记事本是应用中最基础也是最实用的功能之一。无论是快速记录想法、待办事项、还是临时笔记,都需要一个简单易用的记事本工具。本文将介绍如何在Flutter鸿蒙应用中实现一个支持创建、编辑、删除的简易记事本组件。

二、效果展示

在这里插入图片描述

2.1 功能特性

功能 描述
笔记创建 输入内容保存新笔记
笔记编辑 修改已有笔记内容
笔记删除 确认后删除笔记
时间戳 记录笔记创建时间
空状态 无笔记时显示提示
编辑模式 区分新建和编辑状态

三、项目背景与目标

3.1 项目背景

在日常生活和工作中,我们经常需要快速记录一些信息。一个简单易用的记事本可以帮助用户随时记录想法,提高工作效率。

3.2 项目目标

  • 实现笔记的增删改功能
  • 支持时间戳记录
  • 提供友好的用户界面
  • 支持鸿蒙平台运行

四、技术架构设计

4.1 整体架构

┌─────────────────────────────────────┐
│           UI Layer (Widgets)         │
│  ┌──────────┐  ┌──────────┐         │
│  │ TextField│  │ ListView │         │
│  └──────────┘  └──────────┘         │
├─────────────────────────────────────┤
│        State Management              │
│  ┌──────────────────────────────┐   │
│  │    StatefulWidget + State    │   │
│  └──────────────────────────────┘   │
├─────────────────────────────────────┤
│         Business Logic              │
│  ┌────────────┐  ┌───────────────┐  │
│  │    Note    │  │    Note       │  │
│  │  Manager   │  │    Model      │  │
│  └────────────┘  └───────────────┘  │
└─────────────────────────────────────┘

4.2 核心数据结构

final TextEditingController _textController = TextEditingController();
final List<NoteItem> _notes = [];
int? _editingIndex;

class NoteItem {
  final String content;
  final DateTime createdAt;

  NoteItem({
    required this.content,
    required this.createdAt,
  });
}

五、详细实现

5.1 Flutter端实现

import 'package:flutter/material.dart';

class SimpleNotepadPage extends StatefulWidget {
  const SimpleNotepadPage({super.key});

  
  State<SimpleNotepadPage> createState() => _SimpleNotepadPageState();
}

class _SimpleNotepadPageState extends State<SimpleNotepadPage> {
  final TextEditingController _textController = TextEditingController();
  final List<NoteItem> _notes = [];
  int? _editingIndex;

  
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  void _saveNote() {
    final text = _textController.text.trim();
    if (text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入内容')),
      );
      return;
    }

    setState(() {
      if (_editingIndex != null) {
        _notes[_editingIndex!] = NoteItem(
          content: text,
          createdAt: DateTime.now(),
        );
        _editingIndex = null;
      } else {
        _notes.insert(
          0,
          NoteItem(
            content: text,
            createdAt: DateTime.now(),
          ),
        );
      }
      _textController.clear();
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('保存成功')),
    );
  }

  void _editNote(int index) {
    setState(() {
      _editingIndex = index;
      _textController.text = _notes[index].content;
    });
  }

  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('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _notes.removeAt(index);
              });
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已删除')),
              );
            },
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.red,
              foregroundColor: Colors.white,
            ),
            child: const Text('删除'),
          ),
        ],
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('简易记事本'),
        centerTitle: true,
        backgroundColor: Colors.blueGrey,
        foregroundColor: Colors.white,
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.blueGrey.withOpacity(0.1),
              border: Border(
                bottom: BorderSide(color: Colors.grey[300]!),
              ),
            ),
            child: Column(
              children: [
                TextField(
                  controller: _textController,
                  maxLines: 4,
                  decoration: InputDecoration(
                    hintText: '输入笔记内容...',
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                    filled: true,
                    fillColor: Colors.white,
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: ElevatedButton.icon(
                        onPressed: _saveNote,
                        icon: Icon(_editingIndex != null ? Icons.update : Icons.save),
                        label: Text(_editingIndex != null ? '更新' : '保存'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.blueGrey,
                          foregroundColor: Colors.white,
                          padding: const EdgeInsets.symmetric(vertical: 12),
                        ),
                      ),
                    ),
                    if (_editingIndex != null) ...[
                      const SizedBox(width: 8),
                      ElevatedButton.icon(
                        onPressed: () {
                          setState(() {
                            _editingIndex = null;
                            _textController.clear();
                          });
                        },
                        icon: const Icon(Icons.cancel),
                        label: const Text('取消'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.grey,
                          foregroundColor: Colors.white,
                          padding: const EdgeInsets.symmetric(vertical: 12),
                        ),
                      ),
                    ],
                  ],
                ),
              ],
            ),
          ),
          Expanded(
            child: _notes.isEmpty
                ? const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.note_add, size: 64, color: Colors.grey),
                        SizedBox(height: 16),
                        Text(
                          '暂无笔记',
                          style: TextStyle(fontSize: 16, color: Colors.grey),
                        ),
                      ],
                    ),
                  )
                : ListView.builder(
                    padding: const EdgeInsets.all(16),
                    itemCount: _notes.length,
                    itemBuilder: (context, index) {
                      final note = _notes[index];
                      return Card(
                        margin: const EdgeInsets.only(bottom: 12),
                        child: ListTile(
                          contentPadding: const EdgeInsets.all(16),
                          title: Text(
                            note.content,
                            maxLines: 3,
                            overflow: TextOverflow.ellipsis,
                          ),
                          subtitle: Padding(
                            padding: const EdgeInsets.only(top: 8),
                            child: Text(
                              '${note.createdAt.year}-${note.createdAt.month.toString().padLeft(2, '0')}-${note.createdAt.day.toString().padLeft(2, '0')} ${note.createdAt.hour.toString().padLeft(2, '0')}:${note.createdAt.minute.toString().padLeft(2, '0')}',
                              style: TextStyle(color: Colors.grey[600], fontSize: 12),
                            ),
                          ),
                          trailing: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              IconButton(
                                icon: const Icon(Icons.edit, color: Colors.blue),
                                onPressed: () => _editNote(index),
                              ),
                              IconButton(
                                icon: const Icon(Icons.delete, color: Colors.red),
                                onPressed: () => _deleteNote(index),
                              ),
                            ],
                          ),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

class NoteItem {
  final String content;
  final DateTime createdAt;

  NoteItem({
    required this.content,
    required this.createdAt,
  });
}

5.2 UI界面实现

UI界面采用Material Design 3设计风格,主要包含以下组件:

  1. 输入区域:使用TextField组件,支持多行输入
  2. 操作按钮:使用Row布局放置保存/更新和取消按钮
  3. 笔记列表:使用ListView.builder展示笔记卡片
  4. 空状态:使用Column组件显示空状态提示

六、核心功能解析

6.1 笔记保存

区分新建和编辑模式:

void _saveNote() {
  final text = _textController.text.trim();
  if (text.isEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请输入内容')),
    );
    return;
  }

  setState(() {
    if (_editingIndex != null) {
      _notes[_editingIndex!] = NoteItem(
        content: text,
        createdAt: DateTime.now(),
      );
      _editingIndex = null;
    } else {
      _notes.insert(
        0,
        NoteItem(
          content: text,
          createdAt: DateTime.now(),
        ),
      );
    }
    _textController.clear();
  });

  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('保存成功')),
  );
}

6.2 笔记编辑

进入编辑模式:

void _editNote(int index) {
  setState(() {
    _editingIndex = index;
    _textController.text = _notes[index].content;
  });
}

6.3 笔记删除

使用确认对话框:

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('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _notes.removeAt(index);
            });
            Navigator.pop(context);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('已删除')),
            );
          },
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
          ),
          child: const Text('删除'),
        ),
      ],
    ),
  );
}

6.4 时间戳格式化

格式化显示时间:

'${note.createdAt.year}-${note.createdAt.month.toString().padLeft(2, '0')}-${note.createdAt.day.toString().padLeft(2, '0')} ${note.createdAt.hour.toString().padLeft(2, '0')}:${note.createdAt.minute.toString().padLeft(2, '0')}'

七、实际应用场景

7.1 快速笔记

快速记录临时想法、待办事项等。

7.2 会议记录

记录会议要点和决议。

7.3 学习笔记

记录学习内容和知识点。

7.4 购物清单

记录购物清单和待买物品。

八、优化建议

8.1 数据持久化

  • 使用SharedPreferences保存笔记
  • 使用SQLite数据库存储
  • 支持云端同步

8.2 功能扩展

  • 支持笔记分类
  • 添加笔记搜索
  • 支持笔记标签
  • 添加笔记提醒

8.3 用户体验优化

  • 支持富文本编辑
  • 添加笔记导出功能
  • 支持笔记分享
  • 添加笔记排序

九、常见问题与解决方案

9.1 数据丢失问题

问题:应用关闭后笔记丢失

解决方案:使用SharedPreferences或SQLite持久化保存笔记

9.2 编辑状态问题

问题:编辑时删除笔记导致索引错误

解决方案:删除时检查是否在编辑状态,重置编辑索引

9.3 长文本显示问题

问题:长文本显示不完整

解决方案:使用maxLinesTextOverflow.ellipsis限制显示行数

十、总结

本文详细介绍了如何在Flutter鸿蒙应用中实现一个功能完善的简易记事本组件。通过合理的架构设计和清晰的代码实现,我们成功创建了一个支持创建、编辑、删除、时间戳记录的实用组件。该组件可以广泛应用于快速笔记、会议记录、学习笔记等场景,为用户提供便捷的笔记记录服务。

十一、参考资料

Logo

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

更多推荐