创建包含 TextField 的动态列表时,添加或取消列表项时用户输入会丢失(Flutter)

问题描述 投票:0回答:1

下面是我的工作代码的最小复制,其中创建了一个动态列表。该列表已在开始时使用单个元素进行初始化。用户可以在按下按钮时添加更多项目,或者用户可以通过从末尾滑动到开始来关闭该项目。

import 'package:flutter/material.dart';

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

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

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

  @override
  void initState() {
    itemsList = List<InvoiceItemInput>.from([
      InvoiceItemInput(
        parentWidth: 400.0,
        index: 1,
        key: ValueKey('1' + DateTime.now().toString()),
      )
    ]);
    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) => Dismissible(
                    key: ValueKey(index.toString() + DateTime.now().toString()),
                    child: itemsList[index],
                    background: Container(color: Colors.red),
                    direction: DismissDirection.endToStart,
                    onDismissed: (direction) {
                      if (direction == DismissDirection.endToStart) {
                        setState(() {
                          itemsList.removeAt(index);
                        });
                      }
                    }),
                itemCount: itemsList.length,
              ),
              Padding(
                padding: EdgeInsets.symmetric(vertical: 20.0),
                child: Align(
                  alignment: Alignment.centerRight,
                  child: OutlinedButton(
                      child: Text('ADD'),
                      onPressed: () {
                        setState(() {
                          final int index = itemsList.length;
                          itemsList.add(InvoiceItemInput(
                            parentWidth: 400.0,
                            index: index + 1,
                            key: ValueKey(
                                index.toString() + DateTime.now().toString()),
                          ));
                        });
                      }),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class InvoiceItemInput extends StatefulWidget {
  const InvoiceItemInput(
      {super.key, required this.parentWidth, required this.index});
  final double parentWidth;
  final int index;

  @override
  State<InvoiceItemInput> createState() => _InvoiceItemInputState();
}

class _InvoiceItemInputState extends State<InvoiceItemInput> {
  late TextEditingController? itemController;
  final double horizontalSpacing = 15.0;
  bool showDeleteButton = false;

  @override
  void initState() {
    itemController = TextEditingController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(height: 12.0),
        Container(
          padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0),
          child: Text('Item ${widget.index}',
              style: Theme.of(context)
                  .textTheme
                  .labelLarge
                  ?.copyWith(color: Colors.white)),
          decoration: BoxDecoration(
              color: Colors.lightBlue,
              borderRadius: BorderRadius.circular(7.0)),
        ),
        SizedBox(height: 7.0),
        Wrap(
          spacing: this.horizontalSpacing,
          runSpacing: 25.0,
          children: [
            SizedBox(
              width: (widget.parentWidth - horizontalSpacing) / 2 < 200.0
                  ? (widget.parentWidth - horizontalSpacing) / 2
                  : 200.0,
              child: DropdownMenu(
                controller: itemController,
                label: Text(
                  'Item Name *',
                  style: const 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,
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
        SizedBox(height: 15.0),
      ],
    );
  }
}

问题: 添加或取消项目时,文本字段/下拉菜单中的更改将丢失。

寻找: 您能否建议一种方法,以便列表项顶部容器中显示的项目

index
也更新为新值,但保持用户输入完整。

flutter listview
1个回答
0
投票

每次刷新状态时,都会重新创建 InvoiceItemInputs。当然,您在内部定义的 TextEditingController 也会被重新创建。这似乎是问题的根源。

记住:setState方法会触发关联StatefulWidget的build方法。

在这种情况下,如果你想防止数据丢失,你应该在外部定义控制器。

下面,我将提供一个建议:

class _MyAppState extends State<MyApp> {
  late List<InvoiceItemInput> itemsList;
  List<TextEditingController> itemControllers = [];

  @override
  void initState() {
    super.initState();
    itemsList = List<InvoiceItemInput>.from([
      InvoiceItemInput(
        parentWidth: 400.0,
        index: 1,
        controller: TextEditingController(),
        key: ValueKey('1' + DateTime.now().toString()),
      )
    ]);
    itemControllers.add(TextEditingController());
  }

  @override
  void dispose() {
    for (var controller in itemControllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @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) => Dismissible(
                    key: ValueKey(index.toString() + DateTime.now().toString()),
                    child: itemsList[index],
                    background: Container(color: Colors.red),
                    direction: DismissDirection.endToStart,
                    onDismissed: (direction) {
                      if (direction == DismissDirection.endToStart) {
                        setState(() {
                          itemsList.removeAt(index);
                          itemControllers.removeAt(index);
                        });
                      }
                    }),
                itemCount: itemsList.length,
              ),
              Padding(
                padding: EdgeInsets.symmetric(vertical: 20.0),
                child: Align(
                  alignment: Alignment.centerRight,
                  child: OutlinedButton(
                      child: Text('ADD'),
                      onPressed: () {
                        setState(() {
                          final int index = itemsList.length;
                          var controller = TextEditingController();
                          itemControllers.add(controller);
                          itemsList.add(InvoiceItemInput(
                            parentWidth: 400.0,
                            index: index + 1,
                            controller: controller,
                            key: ValueKey(
                                index.toString() + DateTime.now().toString()),
                          ));
                        });
                      }),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class InvoiceItemInput extends StatefulWidget {
  const InvoiceItemInput(
      {super.key,
      required this.parentWidth,
      required this.index,
      required this.controller});
  final double parentWidth;
  final int index;
  final TextEditingController controller;

  @override
  State<InvoiceItemInput> createState() => _InvoiceItemInputState();
}

class _InvoiceItemInputState extends State<InvoiceItemInput> {
  final double horizontalSpacing = 15.0;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(height: 12.0),
        Container(
          padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 7.0),
          child: Text('Item ${widget.index}',
              style: Theme.of(context)
                  .textTheme
                  .labelLarge
                  ?.copyWith(color: Colors.white)),
          decoration: BoxDecoration(
              color: Colors.lightBlue,
              borderRadius: BorderRadius.circular(7.0)),
        ),
        SizedBox(height: 7.0),
        Wrap(
          spacing: this.horizontalSpacing,
          runSpacing: 25.0,
          children: [
            SizedBox(
              width: (widget.parentWidth - horizontalSpacing) / 2 < 200.0
                  ? (widget.parentWidth - horizontalSpacing) / 2
                  : 200.0,
              child: TextField(
                controller: widget.controller,
                decoration: InputDecoration(
                  labelText: 'Item Name *',
                  hintText: 'Enter Item Name',
                ),
              ),
            ),
          ],
        ),
        SizedBox(height: 15.0),
      ],
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.