You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现iPhone Notes式清单:交互功能及状态管理方案咨询

实现类似iPhone Notes清单功能的解决方案

核心思路

不需要引入GetX这类状态管理库,通过Flutter原生状态提升就能实现需求。核心是让父组件统一管理所有清单项的状态(文本、勾选状态、输入控制器),子组件仅负责UI渲染和事件传递,实现增删交互。

步骤1:定义数据类与父组件状态管理

在父组件中维护子任务列表、对应的输入控制器,同时实现增删、状态更新的方法:

class TaskForm extends StatefulWidget {
  const TaskForm({Key? key}) : super(key: key);

  @override
  State<TaskForm> createState() => _TaskFormState();
}

class _TaskFormState extends State<TaskForm> {
  // 存储子任务数据(文本+勾选状态)
  List<SubTask> _subTasks = [];
  // 存储每个输入框的控制器
  List<TextEditingController> _controllers = [];

  @override
  void initState() {
    super.initState();
    // 初始化第一个空清单项
    _addSubTask();
  }

  // 新增清单项
  void _addSubTask({String initialText = ''}) {
    final controller = TextEditingController(text: initialText);
    _subTasks.add(SubTask(text: initialText, isChecked: false));
    _controllers.add(controller);
    setState(() {});
    // 新增后自动聚焦到新输入框
    WidgetsBinding.instance.addPostFrameCallback((_) {
      FocusScope.of(context).requestFocus(FocusNode());
    });
  }

  // 删除清单项(至少保留一个空项)
  void _deleteSubTask(int index) {
    if (_subTasks.length > 1) {
      _controllers[index].dispose();
      _controllers.removeAt(index);
      _subTasks.removeAt(index);
      setState(() {});
      // 删除后聚焦到前一个输入框
      if (index > 0) {
        FocusScope.of(context).requestFocus(FocusNode());
      }
    }
  }

  // 更新勾选状态
  void _onCheckboxChanged(int index, bool value) {
    setState(() {
      _subTasks[index].isChecked = value;
    });
  }

  // 更新输入文本
  void _onTextChanged(int index, String text) {
    setState(() {
      _subTasks[index].text = text;
    });
  }

  @override
  void dispose() {
    // 销毁所有控制器,避免内存泄漏
    for (var controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('清单')),
      body: ListView.builder(
        itemCount: _subTasks.length,
        itemBuilder: (context, index) {
          return SubTaskItem(
            controller: _controllers[index],
            isChecked: _subTasks[index].isChecked,
            onCheckboxChanged: (value) => _onCheckboxChanged(index, value),
            onDelete: () => _deleteSubTask(index),
            onEnter: () => _addSubTask(),
            onTextChanged: (text) => _onTextChanged(index, text),
          );
        },
      ),
    );
  }
}

// 子任务数据模型
class SubTask {
  String text;
  bool isChecked;

  SubTask({required this.text, required this.isChecked});
}

步骤2:重构子组件(SubTaskItem)

将SubTaskItem改为无状态组件,接收父组件传递的状态和回调函数,处理键盘交互:

class SubTaskItem extends StatelessWidget {
  final TextEditingController controller;
  final bool isChecked;
  final ValueChanged<bool> onCheckboxChanged;
  final VoidCallback onDelete;
  final VoidCallback onEnter;
  final ValueChanged<String> onTextChanged;

  const SubTaskItem({
    Key? key,
    required this.controller,
    required this.isChecked,
    required this.onCheckboxChanged,
    required this.onDelete,
    required this.onEnter,
    required this.onTextChanged,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CheckboxListTile(
      value: isChecked,
      onChanged: onCheckboxChanged,
      title: TextFormField(
        controller: controller,
        style: const TextStyle(fontSize: 14.0),
        onChanged: onTextChanged,
        // 处理键盘事件:退格删除、回车新增
        onKeyEvent: (node, event) {
          // 文本为空时按退格,触发删除
          if (event.logicalKey == LogicalKeyboardKey.backspace && controller.text.isEmpty) {
            onDelete();
            return KeyEventResult.handled;
          }
          // 按回车触发新增
          if (event.logicalKey == LogicalKeyboardKey.enter) {
            onEnter();
            return KeyEventResult.handled;
          }
          return KeyEventResult.ignored;
        },
        decoration: const InputDecoration(
          border: InputBorder.none,
          hintText: '输入子任务',
        ),
      ),
      controlAffinity: ListTileControlAffinity.leading,
      activeColor: Colors.green,
    );
  }
}

关键交互说明

  • 退格删除:监听退格键,当输入框文本为空时,调用父组件的_deleteSubTask方法移除当前项。
  • 回车新增:监听回车键,调用父组件的_addSubTask方法添加新的空项,并自动聚焦到新输入框。
  • 焦点管理:新增/删除后自动调整焦点位置,提升用户操作流畅度。

是否需要GetX?

不需要。这个功能逻辑简单,通过Flutter原生的状态提升+setState就能完全实现,代码更轻量且无额外依赖。如果你的项目已经在使用GetX,也可以用GetX来管理列表状态,但对于该需求来说,原生方案是更简便的选择。

内容的提问来源于stack exchange,提问作者Soveyyy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 00:35:29