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

Flutter动态列表增删时TextField/DropdownMenu数据丢失及序号更新问题

解决Flutter动态列表增删时输入内容丢失与序号更新问题

问题说明

实现了支持动态增删的Flutter列表,初始含一个列表项,用户可点击按钮添加新项或滑动删除现有项,但存在两个问题:

  • 添加/删除列表项时,DropdownMenu中的用户输入内容会丢失
  • 删除项后,剩余列表项的序号无法自动更新为正确值

核心修复思路

  • 数据与UI解耦:将每个列表项的输入数据封装为独立Model类,确保输入状态跟随数据而非Widget实例
  • 稳定的Widget Key:使用Model的唯一标识作为列表项和Dismissible的Key,避免Flutter因Key变化重建Widget导致状态丢失
  • 动态计算序号:不在Model中存储序号,而是在构建列表时通过当前索引动态计算,确保增删后序号自动更新

修改后的完整代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// 列表项数据模型,存储输入状态
class InvoiceItemModel {
  final String id;
  TextEditingController itemController;

  InvoiceItemModel({required this.id}) : itemController = TextEditingController();
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late List<InvoiceItemModel> itemsList;

  @override
  void initState() {
    itemsList = [InvoiceItemModel(id: 'item_1')];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SingleChildScrollView(
          child: Column(
            children: [
              ListView.builder(
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) {
                  final item = itemsList[index];
                  return Dismissible(
                    key: ValueKey(item.id),
                    child: InvoiceItemInput(
                      parentWidth: 400.0,
                      index: index + 1, // 动态计算当前序号
                      controller: item.itemController,
                    ),
                    background: Container(color: Colors.red),
                    direction: DismissDirection.endToStart,
                    onDismissed: (direction) {
                      if (direction == DismissDirection.endToStart) {
                        setState(() {
                          itemsList.removeAt(index);
                        });
                      }
                    },
                  );
                },
                itemCount: itemsList.length,
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 20.0),
                child: Align(
                  alignment: Alignment.centerRight,
                  child: OutlinedButton(
                    child: const Text('ADD'),
                    onPressed: () {
                      setState(() {
                        itemsList.add(InvoiceItemModel(id: 'item_${itemsList.length + 1}'));
                      });
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 改为无状态Widget,接收外部传入的控制器和序号
class InvoiceItemInput extends StatelessWidget {
  const InvoiceItemInput({
    super.key,
    required this.parentWidth,
    required this.index,
    required this.controller,
  });

  final double parentWidth;
  final int index;
  final TextEditingController controller;
  final double horizontalSpacing = 15.0;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 12.0),
        Container(
          padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0),
          child: Text(
            'Item $index',
            style: Theme.of(context)
                .textTheme
                .labelLarge
                ?.copyWith(color: Colors.white),
          ),
          decoration: BoxDecoration(
            color: Colors.lightBlue,
            borderRadius: BorderRadius.circular(7.0),
          ),
        ),
        const SizedBox(height: 7.0),
        Wrap(
          spacing: horizontalSpacing,
          runSpacing: 25.0,
          children: [
            SizedBox(
              width: (parentWidth - horizontalSpacing) / 2 < 200.0
                  ? (parentWidth - horizontalSpacing) / 2
                  : 200.0,
              child: DropdownMenu(
                controller: controller,
                label: const Text(
                  'Item Name *',
                  style: TextStyle(
                    fontFamily: 'Raleway',
                    fontSize: 14.0,
                    color: Colors.black87,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                hintText: 'Enter Item Name',
                requestFocusOnTap: true,
                enableFilter: true,
                expandedInsets: EdgeInsets.zero,
                textStyle: Theme.of(context).textTheme.bodySmall,
                menuStyle: MenuStyle(
                  backgroundColor: WidgetStateProperty.all(Colors.lightBlue),
                ),
                dropdownMenuEntries: [
                  DropdownMenuEntry(
                    value: 'Pen',
                    label: 'Pen',
                    style: MenuItemButton.styleFrom(
                      foregroundColor: Colors.white,
                      textStyle: Theme.of(context).textTheme.bodySmall,
                    ),
                  ),
                  DropdownMenuEntry(
                    value: 'Pencil',
                    label: 'Pencil',
                    style: MenuItemButton.styleFrom(
                      foregroundColor: Colors.white,
                      textStyle: Theme.of(context).textTheme.bodySmall,
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
        const SizedBox(height: 15.0),
      ],
    );
  }
}

关键修改点

  • 新增InvoiceItemModel类:存储每个列表项的唯一ID和输入控制器,让输入状态与数据绑定,避免Widget重建丢失内容
  • 替换Widget Key:使用Model的固定ID作为Key,替代原代码中动态生成的DateTime.now(),确保Flutter能正确复用列表项Widget
  • 动态计算序号:在构建InvoiceItemInput时直接传入index+1,删除或添加项后序号自动同步更新
  • 简化列表项Widget:将InvoiceItemInput改为无状态组件,输入控制器由父组件传入,状态统一由Model管理,避免StatefulWidget的状态被意外重置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:18:10