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

时间:2019-05-23 09:02:33

标签: list dart flutter

我正在尝试构建一个页面,用户可以在其中添加和删除列表中的元素。 我正在为一个问题而苦苦挣扎。当我删除元素时,会在更新UI的同时正确更新模型,但是方式错误:仅删除列表的最后一个元素。

我正在使用Flutter 1.5.4。

我已经为列表使用了更简单的元素,并且我尝试仅使用此页面来构建一个新项目以消除所有可能的问题,但是仍然无法正常工作。

我也尝试使用Column而不是List,但结果始终相同。

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个项目添加到列表中,然后删除“项目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]

这是正确的,因为从列表中删除了“项目1”,但是如果我看一下UI,这就是我得到的:

enter image description here

列表中的元素数量正确,模型正确,但是显示的元素错误。

该如何解决?我是在做错什么,还是Flutter的错误?

7 个答案:

答案 0 :(得分:13)

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

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

为什么有效?
它会导致整个ListView从头开始重建自身,但前提是列表的长度发生变化。重要的是,在其他情况下(例如在TextField中键入内容)不会发生这种情况,因为这种操作会使您失去对每种字母类型的关注。

希望它会有所帮助;)

答案 1 :(得分:1)

这不是解决方案,而是一种解释,有关解决方案,请参阅MarcinSzałek的评论。

ListView未按预期方式重新呈现的原因TextFormField是一个有状态的小部件。重建小部件时,需要注意引擎盖下有3棵树,分别是Widget,Element和RenderObject树。 RenderObject是视觉表示,并且直接基于Element。元素直接基于Widget,而Widget只是配置,即您在代码中键入的内容。

首次实例化窗口小部件时,将创建一个对应的元素。但是,当您调用setState()来更改_items列表时,将重新构建窗口小部件,但不会破坏和重新创建该元素。相反,为了最大化效率,Flutter的设计方法是首先比较初始ListTile小部件和重建ListTile小部件之间的差异。在这种情况下,最初有4个图块,然后有3个图块。更新第二个和第三个图块的元素以具有重建ListTile小部件的属性,而不是重新创建新的图块(并且第一个图块没有更改,因为之前和之后的窗口小部件/配置相同) 。从“元素”树中弹出第四个元素。 Flutter官方团队的video在解释这三棵树方面做得很好。

使用Dart的Devtools,您不会发现第三个元素的键/ ID没有变化。当您检查第三个ListTile时,密钥是#3dded 之前和之后。


初始

Phone Initially

删除后

Phone Finally

要从树中删除元素然后放回去,请按MarcinSzałek的评论在ListView上附加一个键。对于有状态的小部件,当相应的新小部件具有相同的小部件类型并具有相同的键时,将更新元素。在没有显式指定键的情况下,该元素将仅检查其类型是否与相应的小部件相同,并且由于所有小部件的运行时类型均为ListTile,因此不会对元素进行任何更新,因此您会看到项目0和项目1而不是项目0和项目2。我建议您阅读有关键here的更多信息。

使用Dart的Devtools,您可以在第三个ListTile的键中找到更改。最初,当它是项目1时,键是#5addd 。然后,当删除项目1时,密钥将更改为#26044


初始

Phone Initially

删除后

Phone Finally

答案 2 :(得分:0)

我认为,您需要为每个文本字段创建控制器或使用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]);
                        }),
                  ),
                );
              },
            )
          ]
        )
    );

答案 3 :(得分:0)

您的窗口小部件状态仅包含字符串列表。当您删除其中一个并重新构建窗口小部件时,Flutter知道您的ListView少包含一个子窗口小部件,因此它将删除最后一个窗口小部件并重建其他窗口小部件。

这里的要点是您使用initialValue来设置TextFormFields上的文本-initialValue的要点是它不会更改字段上的文本即使您更改它-TextFormField只能有1个初始值,这就是您最初设置的值。

如果要更改文本,则需要使用TextEditingController。在上面的示例中,只需将initialValue: item替换为controller: TextEditingController(text: item)就可以解决。但是,在实际的应用程序中,您可能希望将控制器存储在小部件的状态下,以便用户输入的值在整个重建过程中得以持久保存。

答案 4 :(得分:0)

据我了解,setState函数仅更新窗口小部件数据,而不重新创建窗口小部件。在代码中,您使用initialValue为TextField设置默认文本。仅在创建TextField时使用此参数。因此,当您删除一项时,应用程序将根据_item的长度来更新小部件(在这种情况下,请保留前三个,然后删除最后一个)。 initialValue目前没有意义,因为TextField未被创建而是被重用。

要解决此问题,您可以使用TextEditingController而不是initialVal设置文本

首先,使用TextEditingController创建_items地图的列表

  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));

希望有帮助。

答案 5 :(得分:0)

删除不更新已删除元素的原因是第二个小部件是有状态的小部件,在某些情况下需要强制渲染

使用此代码通过覆盖didUpdateWidget方法来重新加载视图

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

答案 6 :(得分:0)

将键或控制器字段用于TextFormField元素。

TextFormField(
    key: GlobalKey(),
    ....
) 
相关问题