Flutter:ListView内部的widget值在移除item时不会更新

Posted

技术标签:

【中文标题】Flutter:ListView内部的widget值在移除item时不会更新【英文标题】:Flutter: The widget values ​inside the ListView are not updated when an item is removed 【发布时间】:2020-09-26 03:47:24 【问题描述】:

您好,我是 Flutter 的新手,我在一个名为 split row 的小部件中工作,我使用具有三个子组件的 ListView:Text Widget、ListView Builder 小部件和一个 Container Widget。

我使用文本作为标题,使用地图填充 ListView Builder,使用容器将小部件添加到 ListView Builder,一切正常,但是当我从地图中删除一个项目时,从 ListView生成器也是,但不更新值。

为了更好地解释我自己,我用一个 textField 和一个 Text 做了一个例子,第一次我输入了 map 键的值 3 次,第二次用 word data 和 map 键形成了文本。这就是发生的事情

正如您所见,当我删除 data1 时,它消失的项目只留下数据 0、数据 2 和数据 3,但值 111 仍然存在,值 333 消失。显然values不对应map元素除了000 => data 0

我已经在论坛中进行了研究,我已经在网站上搜索了 ***,但我没有找到任何对我有帮助的东西

这是代码:

import 'package:flutter/material.dart';

class SplitRow extends StatefulWidget 
  @override
  _SplitRowState createState() => _SplitRowState();


class _SplitRowState extends State<SplitRow> 
  int _index = 0;
  Map<String, Widget> splitRows = ;
  Map records = ;
  Map col1 = ;

  // Crete Widget Split Row
  Widget _splitRow(index) 
    return Column(
      children: <Widget>[
        Container(
          color: Colors.white,
          height: 40.0,
          child: Row(
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.remove_circle),
                iconSize: 24,
                color: Color(0xFFFF3B30),
                onPressed: () 
                  removeItem(index);
                ,
              ),
              Container(
                width: 100,
                child: TextField(
                  keyboardType: TextInputType.numberWithOptions(
                    signed: false,
                    decimal: true,
                  ),
                  decoration: InputDecoration(
                    hintText: 'Put direction here...',
                    border: InputBorder.none,
                  ),
                  style: TextStyle(fontFamily: 'Lekton'),
                  onChanged: (value) 
                    col1["$index"] = value;
                  ,
                ),
              ),
              _VerticalDivider(),
              Text('data $index'),
            ],
          ),
        ),
        _HorizontalDivider(),
      ],
    );
  

  // Create Widget Add Row
  Widget _addRow() 
    return Container(
      height: 40,
      color: Colors.white,
      child: Row(
        children: <Widget>[
          Container(
            height: 40,
            color: Colors.white,
            child: IconButton(
              icon: Icon(Icons.add_circle),
              iconSize: 24,
              color: Color(0xFF34C759),
              onPressed: () 
                addItem();
              ,
            ),
          ),
          Text('New Item')
        ],
      ),
    );
  

  // Add splitRow
  void addItem() 
    int key = _index;
    print('Index antes $_index');
    setState(() 
      splitRows['$key'] = _splitRow(key);
      ++_index;
    );
    print(splitRows);
  

  // Remove splitRow
  void removeItem(key) 
    setState(() 
      splitRows.remove('$key');
      col1.remove('$key');
    );
  

  // Save Values
  void saveItems() 
    records = 'qty': col1;
    print(records);
  

  @override
  void initState() 
    super.initState();
    splitRows['$_index'] = _splitRow(_index);
    ++_index;
    // splitRows['$_index'] = _addRow(_index);
    // ++_index;
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
        appBar: AppBar(
          title: Text('Directions'),
          actions: <Widget>[
            FlatButton(
              child: Text(
                'Save',
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () 
                saveItems();
              ,
            )
          ],
        ),
        backgroundColor: Color(0xFFE5E5EA),
        body: Column(
          children: <Widget>[
            Container(
                constraints: BoxConstraints(
                  minHeight: 40.0,
                  maxHeight: 500.0,
                  minWidth: double.infinity,
                  maxWidth: double.infinity,
                ),
                child: ListView(
                  children: <Widget>[
                    Padding(padding: EdgeInsets.only(top: 10.0)),
                    Text('DIRECTIONS'),
                    ListView.builder(
                      shrinkWrap: true,
                      itemCount: splitRows.length,
                      itemBuilder: (BuildContext context, int index) 
                        String key = splitRows.keys.elementAt(index);
                        return splitRows[key];
                      ,
                      physics: NeverScrollableScrollPhysics(),
                    ),
                    _addRow()
                  ],
                ))
          ],
        ));
  


class _HorizontalDivider extends StatelessWidget 
  const _HorizontalDivider(
    Key key,
  ) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Container(
      color: Colors.white,
      child: Divider(
        height: 2.0,
      ),
    );
  


class _VerticalDivider extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Container(
      height: 20,
      width: 2,
      margin: EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
      child: VerticalDivider(
        width: 2.0,
      ),
    );
  

我将永远感激你能给我的任何帮助或建议,我已经很久没有解决方案了

【问题讨论】:

您应该返回一个新创建的小部件而不是 return splitRows[key];,因此不要将小部件存储在您的 splitRows 地图中,而是仅存储数据并在构建器中按需创建小部件 谢谢@pskink 你的意思是这样的:return _splitRow(key)?对不起,我是颤振的新手。再次感谢 我的意思是你不应该在你的地图中缓存任何小部件,而是你应该只保留你的数据并在 builder 中按需构建小部件 - 就像这里:flutter.dev/docs/cookbook/lists/long-lists 你有 items 变量只保留数据 完美,我去上班,谢谢 【参考方案1】:

ListView.builder 构建要显示的小部件列表,当您删除一个小部件并更新状态时 ListView.builder 只需重新运行要构建的小部件并查看 TextField 已经存在,比较看起来像的元素相同类型的小部件,所以它不会改变任何东西,Textfield 也不会保存任何保存的状态,所以即使要重建它也会返回“将方向放在这里......”。即使您认为您将小部件的状态保存在不会更改任何内容的 Map 中,要构建的小部件也会保存但没有状态。您可以添加一个 Key 以使它们看起来不同,或者向 TextField 添加一个 Textcontroller(它不会保存状态,但您可以为其添加初始文本值)

class MySplitRow extends StatelessWidget
  final Map col1;
  final String index;
  final VoidCallback onPressed;

  MySplitRow(this.col1, this.index, this.onPressed, Key key) : super(key: key);


  @override
  Widget build(BuildContext context)
    return Column(
      children: <Widget>[
        Container(
          color: Colors.white,
          height: 40.0,
          child: Row(
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.remove_circle),
                iconSize: 24,
                color: Color(0xFFFF3B30),
                onPressed: onPressed,
              ),
              Container(
                width: 100,
                child: TextField(
                  controller: TextEditingController(text: col1["$index"]), //it will check the map col1 and if its not null it will give you the value that was saved at that key
                  keyboardType: TextInputType.numberWithOptions(
                    signed: false,
                    decimal: true,
                  ),
                  decoration: InputDecoration(
                    hintText: 'Put direction here...',
                    border: InputBorder.none,
                  ),
                  style: TextStyle(fontFamily: 'Lekton'),
                  onChanged: (value) 
                    col1["$index"] = value;
                  ,
                ),
              ),
              const VerticalDivider(width: 10, endIndent: 10, indent: 10), //Works exactly as _VerticalDivider with less code
              Text('data $index')
            ],
          ),
        ),
        const Divider(color: Colors.white, height: 2) //Works exactly as _HorizontalDivider with less code
      ],
    );
  

在 Listview.builder 中

 (BuildContext context, int index) 
  String key = splitRows.keys.elementAt(index);
  return MySplitRow(
     key: ValueKey<String>(key),
     col1: col1,
     index: key,
     onPressed: () => removeItem(key),
  );

一些建议,更喜欢使用类而不是方法来构建小部件。您的 _Horizo​​ntalDivider 和 _VerticalDivider 可以在不将 Divider 包装在容器中的情况下构建

【讨论】:

感谢您的帮助,除了帮我解决问题,他还给我留下了更多的知识

以上是关于Flutter:ListView内部的widget值在移除item时不会更新的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 在 ListView 中不显示有状态的子 Widget

Flutter学习-滚动的Widget

Flutter控件篇(Stateful widget)——ListView

flutter listview item滚出屏幕不重置状态

Flutter Widgets 之 ListWheelScrollView

Flutter Widgets 之 ListWheelScrollView