设计理念

文件夹是组织笔记的重要方式,就像电脑上的文件系统一样,可以帮助用户建立清晰的层级结构。一个好的文件夹管理系统不仅要支持创建和删除,还要能够展示层级关系,让用户一目了然。本文将详细介绍如何实现一个功能完善的文件夹列表页面。
请添加图片描述

页面基础结构

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/note_controller.dart';
import '../../models/category.dart';
import '../notes/widgets/empty_state.dart';
import 'folder_detail_page.dart';

class FolderPage extends StatelessWidget {
  const FolderPage({super.key});

这段代码导入了页面所需的核心依赖。Material提供基础UI组件,GetX负责状态管理和路由导航,ScreenUtil确保在不同屏幕尺寸下的适配效果。NoteController是数据管理的核心,通过依赖注入的方式获取实例。EmptyState组件用于展示空列表状态,提升用户体验。FolderPage采用StatelessWidget设计,所有状态变化都由GetX的响应式系统处理。

构建页面框架

  
  Widget build(BuildContext context) {
    final controller = Get.find<NoteController>();
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('文件夹'),
        actions: [
          IconButton(
            icon: const Icon(Icons.create_new_folder),
            onPressed: () => _showCreateFolderDialog(context, controller),

build方法首先通过Get.find获取NoteController实例,这是GetX依赖注入的标准用法。Scaffold提供了页面的基本结构,包含AppBar和body两部分。AppBar展示"文件夹"标题,右侧actions区域放置创建按钮,使用create_new_folder图标直观表达功能。点击按钮触发_showCreateFolderDialog方法,弹出创建对话框。这种设计将操作入口集中在顶部,符合用户的操作习惯。

响应式列表渲染

          ),
        ],
      ),
      body: Obx(() {
        final rootFolders = controller.folders
            .where((f) => f.parentId == null)
            .toList();
        if (rootFolders.isEmpty) {
          return const EmptyState(
            icon: Icons.folder_outlined,

body部分使用Obx包裹,实现响应式UI更新。当controller.folders数据变化时,页面会自动重新渲染。通过where过滤出parentId为null的根级文件夹,避免将子文件夹误显示在顶层。isEmpty判断用于处理空列表场景,此时返回EmptyState组件。EmptyState使用folder_outlined图标,视觉上与文件夹主题保持一致。这种设计确保用户在任何数据状态下都能获得清晰的界面反馈。

空状态处理

            title: '暂无文件夹',
            subtitle: '点击右上角创建文件夹',
          );
        }
        return ListView.builder(
          padding: EdgeInsets.all(12.w),
          itemCount: rootFolders.length,
          itemBuilder: (context, index) {
            final folder = rootFolders[index];
            final subFolders = controller.folders

EmptyState的title和subtitle为用户提供明确的操作指引,降低学习成本。当有数据时,使用ListView.builder构建列表,这是Flutter推荐的高性能列表渲染方案。padding使用ScreenUtil的w单位,确保在不同屏幕密度下保持一致的视觉效果。itemCount设置为rootFolders.length,确保只渲染必要的条目。在itemBuilder中,先获取当前folder对象,然后筛选出该文件夹的子文件夹。这种按需计算的方式避免了不必要的性能开销。

计算文件夹统计信息

                .where((f) => f.parentId == folder.id)
                .toList();
            final noteCount = controller
                .getNotesByFolder(folder.id)
                .length;
            
            return Card(
              margin: EdgeInsets.only(bottom: 8.h),
              child: ExpansionTile(

通过where过滤出parentId等于当前folder.id的子文件夹,建立父子关系。调用controller.getNotesByFolder获取该文件夹下的笔记列表,通过length属性得到笔记数量。这些统计信息将在UI中展示,帮助用户了解文件夹的内容规模。Card组件为每个文件夹项提供独立的视觉容器,margin设置底部间距,使列表更加清晰。ExpansionTile是可展开的列表项,非常适合展示层级结构。

文件夹主体信息展示

                leading: const Icon(Icons.folder, color: Color(0xFF2196F3)),
                title: Text(folder.name),
                subtitle: Text('$noteCount 篇笔记'),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                      icon: const Icon(Icons.add, size: 20),
                      onPressed: () => _showCreateFolderDialog(
                          context, controller, parentId: folder.id),

leading使用蓝色文件夹图标,提供视觉识别点。title显示文件夹名称,subtitle展示笔记数量统计,让用户快速了解内容规模。trailing区域使用Row布局,放置添加和删除两个操作按钮。mainAxisSize.min确保Row只占用必要的宽度。添加按钮点击后调用_showCreateFolderDialog,并传入parentId参数,表示要在当前文件夹下创建子文件夹。这种设计将层级创建的入口直接放在父文件夹上,操作路径更短。

文件夹操作按钮

                    ),
                    IconButton(
                      icon: const Icon(Icons.delete_outline, size: 20),
                      onPressed: () => _confirmDelete(context, controller, folder),
                    ),
                  ],
                ),
                onExpansionChanged: (expanded) {},
                children: [
                  ListTile(

删除按钮使用delete_outline图标,视觉上比实心图标更轻量,避免过于突出危险操作。点击触发_confirmDelete方法,弹出二次确认对话框,防止误删。onExpansionChanged回调在展开/收起时触发,这里暂时为空实现,预留扩展空间。children数组定义展开后显示的内容,第一项是"查看笔记"入口。这种设计将文件夹的详情查看放在展开区域,保持主列表的简洁性。

展开区域内容

                    leading: const Icon(Icons.arrow_forward),
                    title: const Text('查看笔记'),
                    onTap: () => Get.to(() => FolderDetailPage(folder: folder)),
                  ),
                  ...subFolders.map((sf) => ListTile(
                    leading: const Icon(Icons.folder_open, size: 20),
                    title: Text(sf.name),
                    subtitle: Text('${controller.getNotesByFolder(sf.id).length} 篇笔记'),
                    onTap: () => Get.to(() => FolderDetailPage(folder: sf)),

"查看笔记"ListTile使用arrow_forward图标,暗示点击后会进入新页面。点击后通过Get.to导航到FolderDetailPage,传入当前folder对象。展开运算符…将subFolders数组展开,为每个子文件夹生成一个ListTile。子文件夹使用folder_open图标,与父文件夹的folder图标形成视觉区分。subtitle同样显示笔记数量,保持信息展示的一致性。点击子文件夹也会进入详情页,实现层级导航。

子文件夹删除操作

                    trailing: IconButton(
                      icon: const Icon(Icons.delete_outline, size: 18),
                      onPressed: () => _confirmDelete(context, controller, sf),
                    ),
                  )),
                ],
              ),
            );
          },
        );
      }),
    );
  }

子文件夹的trailing区域也放置删除按钮,图标尺寸略小(18),视觉上与层级关系匹配。删除操作同样调用_confirmDelete方法,传入子文件夹对象sf。map方法的结果通过展开运算符添加到children数组中,形成完整的展开内容。多层闭合括号结束ExpansionTile、Card、itemBuilder、ListView.builder、Obx和Scaffold的定义。这种层级结构清晰地展示了父子文件夹关系,用户可以直观地理解和操作。

创建文件夹对话框

  void _showCreateFolderDialog(
      BuildContext context, 
      NoteController controller, 
      {String? parentId}) {
    final nameController = TextEditingController();
    
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(parentId == null ? '新建文件夹' : '新建子文件夹'),
        content: TextField(

_showCreateFolderDialog方法接收可选的parentId参数,用于区分创建根文件夹还是子文件夹。TextEditingController用于管理输入框的文本内容,这是Flutter表单处理的标准方式。showDialog弹出模态对话框,builder返回AlertDialog组件。title根据parentId是否为null动态显示不同文本,让用户明确当前操作的上下文。content区域放置TextField输入框,用于输入文件夹名称。这种设计通过一个方法复用了两种创建场景的逻辑。

输入框配置

          controller: nameController,
          decoration: const InputDecoration(
            labelText: '文件夹名称',
            border: OutlineInputBorder(),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),

TextField绑定nameController,实现双向数据绑定。InputDecoration配置输入框的外观,labelText提供占位提示,border使用OutlineInputBorder样式,提供清晰的输入边界。actions数组定义对话框底部的操作按钮,第一个是取消按钮。TextButton是Material Design推荐的文本按钮样式,点击后调用Navigator.pop关闭对话框。这种设计为用户提供了明确的退出路径,避免误操作。

创建确认逻辑

          ElevatedButton(
            onPressed: () {
              if (nameController.text.isNotEmpty) {
                controller.createFolder(
                    nameController.text, 
                    parentId: parentId);
                Navigator.pop(context);
              }
            },
            child: const Text('创建'),
          ),
        ],
      ),
    );
  }

创建按钮使用ElevatedButton,视觉上比TextButton更突出,引导用户完成主要操作。onPressed回调中先检查输入是否为空,避免创建无效文件夹。验证通过后调用controller.createFolder方法,传入名称和parentId参数。controller负责数据持久化和状态更新,页面层保持轻量。创建成功后立即关闭对话框,用户会看到列表自动更新。这种即时反馈提升了操作的流畅性。

删除确认对话框

  void _confirmDelete(
      BuildContext context, 
      NoteController controller, 
      Folder folder) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('删除文件夹'),
        content: Text('确定要删除"${folder.name}"吗?文件夹内的笔记将移至未分类。'),
        actions: [

_confirmDelete方法实现删除前的二次确认,这是危险操作的标准交互模式。AlertDialog的title明确操作类型,content详细说明删除后果。特别提示"笔记将移至未分类",让用户了解数据不会丢失,只是改变了组织方式。这种透明的信息披露降低了用户的操作焦虑。将folder.name嵌入提示文本中,帮助用户确认操作对象,避免删错。

删除操作执行

          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          ElevatedButton(
            style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red),
            onPressed: () {
              controller.deleteFolder(folder.id);
              Navigator.pop(context);
            },
            child: const Text('删除'),
          ),
        ],
      ),
    );
  }
}

取消按钮提供安全退出路径,点击后仅关闭对话框,不执行任何操作。删除按钮使用红色背景,这是危险操作的通用视觉语言,提醒用户谨慎操作。点击后调用controller.deleteFolder方法,传入folder.id进行精确删除。删除操作由controller处理,包括数据库更新、笔记重新分类等复杂逻辑。操作完成后关闭对话框,Obx会自动触发列表刷新,用户立即看到结果。这种设计将业务逻辑与UI层完全分离,提高了代码的可维护性。

总结

文件夹列表页面是笔记应用的核心功能之一,通过ExpansionTile实现了层级展示,用户可以清晰地看到父子文件夹关系。创建和删除操作都提供了友好的对话框交互,确保操作的准确性和安全性。GetX的响应式系统让数据变化自动反映到UI上,无需手动管理状态更新。ScreenUtil确保了在不同设备上的一致体验。整体设计遵循Material Design规范,提供了直观、高效的文件夹管理体验。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐