Flutter -删除元素时,UI未正确更新

agxfikkp  于 2023-02-20  发布在  Flutter
关注(0)|答案(8)|浏览(229)

我试图构建一个页面,用户可以在其中添加和删除列表中的元素。我遇到了一个问题。当我删除一个元素时,模型会正确更新,同时UI也会更新,但方式错误:仅移除列表的最后一个元素。
我用的是Flutter 1.5.4版。
我已经在列表中使用了更简单的元素,并且我试图用这个页面来构建一个新项目,以消除所有可能的问题,但它仍然不能正确工作。
我也试过用列代替列表,但结果总是一样。
main.dart:

import 'package:flutter/material.dart';
import './widget.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Hello'),
        ),
        body: Center(
          child: SectionWidget(),
        ),
      ),
    );
  }
}

widget.dart:

import 'package:flutter/material.dart';

class SectionWidget extends StatefulWidget {
  _SectionWidgetState createState() => new _SectionWidgetState();
}

class _SectionWidgetState extends State<SectionWidget> {
  List<String> _items = List<String>();

  @override
  Widget build(BuildContext context) {
    List<Widget> children = [
      ListTile(
          title: Text(
            "Strings",
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
          trailing: IconButton(
            icon: Icon(Icons.add_circle),
            onPressed: () => setState(() {
                  _items.add('item ${_items.length}');
                  print("Adding "+ _items.last);
                }),
          ),
        ),
    ];

    children.addAll(_buildForms());

    return ListView(
      children: children,
    );
  }

  List<Widget> _buildForms() {
    List<Widget> forms = new List<Widget>();
    print("Strings:" + _items.toString());
    for (String item in _items) {
      forms.add(
        ListTile(
          leading: Icon(Icons.person),
          title: TextFormField(
            initialValue: item,
          ),
          trailing: IconButton(
            icon: Icon(Icons.delete),
            onPressed: () => setState(() {
                  print("Removing $item");
                  _items.remove(item);
                }),
          ),
        ),
      );
    }
    return forms;
  }
}

如果我向列表中添加4项,然后删除“item 1”,则控制台上的输出如下:

I/flutter ( 4192): Strings:[]
I/flutter ( 4192): Adding item 0
I/flutter ( 4192): Strings:[item 0]
I/flutter ( 4192): Adding item 1
I/flutter ( 4192): Strings:[item 0, item 1]
I/flutter ( 4192): Adding item 2
I/flutter ( 4192): Strings:[item 0, item 1, item 2]
I/flutter ( 4192): Adding item 3
I/flutter ( 4192): Strings:[item 0, item 1, item 2, item 3]
I/flutter ( 4192): Removing item 1
I/flutter ( 4192): Strings:[item 0, item 2, item 3]

这是正确的,因为“item 1”已从列表中删除,但如果我查看UI,则会看到以下内容:

列表中的元素数量正确,型号正确,但显示的元素错误。
我该如何解决这个问题?是我做错了什么还是Flutter的bug?

qcbq4gxm

qcbq4gxm1#

您可以做的是将Key传递给ListViewKey将包含列表中元素的长度。

ListView(
  key: Key(myList.length.toString()),
  children: myList.map(...),
),

为什么有效

它会导致整个ListView从头开始重建,但只有当列表的长度改变时才会发生。重要的是,它不会在其他情况下发生(比如在TextField中键入),因为这样的操作会导致您无法关注每一个字母类型。
希望能有所帮助;)

xyhw6mcr

xyhw6mcr2#

这不是一个解决方案,而是一个解释,参见Marcin Szałek对解决方案的评论。

ListView没有按预期重新呈现的原因TextFormField是一个有状态的小部件。当您重新构建小部件时,您需要注意有3棵树,它们是Widget、Element和RenderObject树。RenderObject是可视化表示,直接基于Element。而Element直接基于Widget,而Widget只是配置,即您在代码中键入的内容。
首次示例化小部件时,会创建一个对应的元素。但是,当您调用setState()更改_items列表时,会重新构建小部件,但不会破坏和重新创建元素。相反,为了最大化效率,Flutter会首先比较初始ListTile小部件和重新构建的ListTile小部件之间的任何差异。在这种情况下,最初有4个瓦片,之后有3个瓦片。代替重新创建新瓦片,更新第二和第三个切片的元素,使其具有rebuild ListTile小部件的属性(并且第一个图块没有发生变化,因为前后的小部件/配置是相同的).第4个元素从元素树中弹出。官方Flutter团队的这款X1 E0 F1 X在解释这三棵树方面做得很好。
使用Dart的Devtools,您可以发现第三个元素的key/id没有任何变化,当您检查第三个ListTile时,在key之前和之后都是 *#3ded *。

姓名首字母

第一节第一节第一节第一节第一次

删除后

第一节第二节第一节第三节第一节
要从树中删除元素然后再放回去,请在ListView上附加一个key,如Marcin Szałek所述。对于有状态的小部件,当对应的新小部件具有相同的小部件类型和key时,元素将被更新。如果没有显式指定key,元素将只检查它是否与对应的小部件具有相同的类型。由于所有的小部件都是运行时类型ListTile,因此不会有任何元素的更新,因此您看到的是item 0和item 1,而不是item 0和item 2。我建议您阅读更多关于key here的内容。
使用Dart的Devtools,可以发现第三个ListTile的键发生了变化:最初,当它是item 1时,键是 #5addd,然后,当item 1被删除时,键变为 #26044

姓名首字母

一个四个一个一个一个五个一个

删除后

第一节第六节第一节第一节第七节第一节

qxsslcnc

qxsslcnc3#

删除而不更新已删除元素的原因是第二个小部件是有状态小部件,在某些情况下需要强制呈现
使用以下代码通过覆盖didUpdateWidget方法来获取reload视图

@override
  void didUpdateWidget(LoadImageFromPathAsync oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget != widget) {
        setState(() {});
    }
  }
jtw3ybtb

jtw3ybtb4#

我尝试Marcin Szałek解决方案,但我使用BLoC来重建小部件,因此他的解决方案导致了如下问题:
1-将第一个元素(TextField)添加到列表中并在其中键入“a
2-将第二个元素(TextField)添加到列表中(第一个TextField将变为空)
实际上我不知道如果我们不使用BLoC是否会发生这种情况,但如果你使用BLoC,试试这个解决方案,它对我来说就像一个魅力:
1-创建UniqueKey的列表

List<UniqueKey> listKeys = [];

2-当你点击添加图标添加一个元素到ListView时,确保添加UniqueKey对象到listKeys

listKeys.add(UniqueKey());

3-将每个listKeys列表成员附加到ListView

ListView.builder(
   shrinkWrap: true,
   itemCount: items?.length ?? 0,
   itemBuilder: (context, index) {
         return ListItem(
            key: listKeys[index];
      );
    },
   )

4-从ListView中删除元素“TextField”时,必须将其从listKyes中删除以

listKeys.removeAt(deleteIndex);
cs7cruho

cs7cruho5#

我认为,你需要为每个文本域创建控制器或者使用hintText

Scaffold(
      appBar: AppBar(
        title: Text('Hello'),
      ),
      body: ListView(
          children: <Widget>[
            ListTile(
              title: Text(
                'Strings',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              trailing: IconButton(
                icon: Icon(Icons.add_circle),
                onPressed: () => setState(() {
                      _items.add('item ${_items.length}');
                      print('Adding '+ _items.last);
                    }),
              ),
            ),
            ListView.builder(
              physics: BouncingScrollPhysics(),
              shrinkWrap: true,
              itemCount: _items.length,
              itemBuilder: (BuildContext context, int i){
                return ListTile(
                  leading: Icon(Icons.person),
                  title: TextFormField(
                    decoration: InputDecoration(
                      hintText: _items[i]
                    ),
                  ),
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: () => setState(() {
                          print('Removing ${_items[i]}');
                          _items.remove(_items[i]);
                        }),
                  ),
                );
              },
            )
          ]
        )
    );
j13ufse2

j13ufse26#

你的小部件状态只包含String列表,当你删除其中一个并重建小部件时,Flutter知道你的ListView少了一个子小部件,所以它删除最后一个并重建其他的。
这里的问题是,您使用initialValue设置TextFormFields上的文本-initialValue的全部意义在于,即使您更改字段上的文本,它也不会更改它-TextFormField只能有1个初始值,这就是您最初设置的值。
如果你想改变文本,你需要使用一个TextEditingController。在上面的例子中,简单地用controller: TextEditingController(text: item)替换initialValue: item就可以解决这个问题。在一个真实的的应用程序中,你可能想把控制器存储在小部件的状态中,这样用户输入的值就可以在重建过程中保持不变。

xsuvu9jc

xsuvu9jc7#

据我所知,setState函数只更新窗口小部件数据,不重新创建窗口小部件。在您的代码中,您使用initialValue设置TextField的默认文本。此参数仅在创建TextField时使用。因此,当您删除一个项目时,应用程序将根据_item的长度更新窗口小部件(在本例中,保留3个第一个,删除最后一个)。此时initialValue没有意义,因为TextField不是创建的,而是重用的。
要解决此问题,可以使用TextEditingController而不是initialVal来设置文本
首先,使用_items创建TextEditingControllerMap列表

List<TextEditingController> controllers = [];

然后,当添加一个项目也创建一个新的控制器

onPressed: () => setState(() {
  _items.add('item ${_items.length}');
  controllers.add(TextEditingController(text:  "${_items.length}"));
  print("Adding "+ _items.last);
}),

最后,将控制器添加到TextField

TextFormField(
  controller: controllers[_items.indexOf(item)],
)

啊,不要忘记删除项目之前删除控制器

controllers.removeAt(_items.indexOf(item));

希望有帮助。

yptwkmov

yptwkmov8#

为TextFormField元素使用键或控制器字段。

TextFormField(
    key: GlobalKey(),
    ....
)

相关问题