【问题标题】:How to dynamically update a particular list view item while using a ListView.builder?如何在使用 ListView.builder 时动态更新特定的列表视图项?
【发布时间】:2020-12-12 10:00:56
【问题描述】:

我正在 Flutter 中制作列表视图。我想在长按项目时更新项目的属性。

以下是完整的代码:

// main.dart
import 'package:LearnFlutter/MyList.dart';
import 'package:flutter/material.dart';
import 'MyList.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'My list demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: MyList(),
    );
  }
}

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

class Item {
  Item(String name, bool selected, Color color) {
    _name = name;
    _selected = selected;
    _color = color;
  }

  String _name;
  bool _selected;
  Color _color;

  String getName() {
    return _name;
  }

  bool isSelected() {
    return _selected;
  }

  void toggleSelected() {
    _selected = !_selected;
  }

  void setColor(Color color) {
    _color = color;
  }

  Color getColor() {
    return _color;
  }
}

class MyList extends StatefulWidget {
  @override
  _CardyState createState() => new _CardyState();
}

class _CardyState extends State<MyList> {
  @override
  Widget build(BuildContext context) {
    var itemsList = [
      Item('My item1', false, Colors.grey[200]),
      Item('My item2', false, Colors.grey[200]),
      Item('My item3', false, Colors.grey[200]),
    ];

    return ListView.builder(
      itemCount: itemsList.length,
      itemBuilder: (context, index) {
        return Card(
          child: ListTile(
            leading: Icon(Icons.train),
            title: Text(itemsList[index].getName()),
            trailing: Icon(Icons.keyboard_arrow_right),
            tileColor: itemsList[index].getColor(),
            selected: itemsList[index].isSelected(),
            onLongPress: () {
              toggleSelection(itemsList[index]);
            },
          ),
        );
      },
    );
  }

  void toggleSelection(Item item) {
    print(item.getName() + ' long pressed');
    setState(() {
      item.toggleSelected();
      if (item.isSelected()) {
        item.setColor(Colors.blue[200]);
      } else {
        item.setColor(Colors.grey[200]);
      }
    });
  }
}

问题:
在上面的代码中,toggleSelection 在长按事件中被调用。但是该项目的颜色不会更新。我做错了什么?

【问题讨论】:

  • MyList 变成stateless widget 保留它应该在HomePage 中显示的所有数据,HomePage 是一个有状态的小部件,包括有关所选项目的数据。然后将数据传入MyList
  • 感谢您的浏览。你能用代码回答这个问题吗?无论如何,我正在尝试您的解决方案,但使用一些代码或一些伪代码的答案会更清楚。

标签: flutter listview dart


【解决方案1】:

它无法正常运行的主要原因是您的班级Item没有状态,因此您没有重新构建/更新任何东西。如果你想在课堂上处理它,那么你需要将它扩展到ChangeNotifier。您还需要使用ChangeNotifierProvider,查看文档以获取帮助:https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple

您将需要提供程序包:https://pub.dev/packages/provider

类项

class Item extends ChangeNotifier {
  Item(String name, Color color) {
    _name = name;
    _color = color;
  }

  int selectedIndex; // to know active index
  String _name;
  Color _color;

  String getName() {
    return _name;
  }

  void toggleSelected(int index) {
    selectedIndex = index;

    notifyListeners(); // To rebuild the Widget
  }

  void setColor(Color color) {
    _color = color;

    notifyListeners();
  }

  Color getColor() {
    return _color;
  }
}

小部件列表

class MyList extends StatefulWidget {
  @override
  _CardyState createState() => new _CardyState();
}

class _CardyState extends State<MyList> {
  @override
  Widget build(BuildContext context) {
    final items = Provider.of<Item>(context); // Accessing the provider

    bool selected = false; // default val. of bool

    var itemsList = [
      Item('My item1', Colors.grey[200]),
      Item('My item2', Colors.grey[200]),
      Item('My item3', Colors.grey[200]),
    ];

    return ListView.builder(
      itemCount: itemsList.length,
      itemBuilder: (context, index) {
        return Card(
          child: ListTile(
            leading: Icon(Icons.train),
            title: Text(itemsList[index].getName()),
            trailing: Icon(Icons.keyboard_arrow_right),
            tileColor: items.selectedIndex == index
                ? items.getColor()
                : Colors.grey[200],
            selected: items.selectedIndex == index ? true : false,
            onLongPress: () {
              setState(() => selected = !selected);

              items.toggleSelected(index);

              if (selected) {
                items.setColor(Colors.red);
              }
            },
          ),
        );
      },
    );
  }
}

【讨论】:

  • 非常感谢您解释了根本原因。接受答案。出于好奇,我只有一个问题。我希望我会在setState 中对toggleSelected 进行呼叫,但那不是您所做的。相反,这行代码setState(() =&gt; selected = !selected) 看起来像在做这个伎俩。为什么setColor 没有包裹在setState 中?你能解释一下这件小事吗?
  • 嘿,当然,没问题。由于我在 Stateful 小部件本身中更改了selected = !selectedstate,因此我可以使用setState,因为它属于状态对象。我没有将setColor 包装在setState 中的原因是因为我已经使用notifyListeners 更改了类中的状态。这将通知有状态的小部件并重建它。
  • 啊哈!知道了。谢谢。
【解决方案2】:

MyList 制作成一个无状态小部件,保留它应该在HomePage 中显示的所有数据,statefull widget 包括有关所选项目的数据。然后将数据传入MyList

您的 MyList 可能是这样的

class MyList extends StatelessWidget {
  final List<Item> items;
  final List<int> selectedItemIdList;
  final void Function(Item) onLongClick;

  MyList(this.items, this.selectedItemIdList, this.onLongClick);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, position) {
        //remember all you need to do here is to create your item based on the data you have
        var item = items[position];
        var isSelected = items.firstWhere((element) => item.id == element.id) != null;
        if (isSelected) {
          //build and return a widget with selected look
        } else {
          return GestureDetector(
            onLongPress: () => onLongClick(item), //changes data in homepage then MyList will be updated automatically
            child: Container(
              //rest of your widget
            ),
          );
        }
      },
      itemCount: items.length,
    );
  }
}

在您的 HomePageState 中


//all the data the list it build from should be stored here not inside the list. and 

List<Item> items = [ ... ];
List<int> selectedItemIdList = [ ... ];

//MyList is just a Stateless widget that only shows this data change (a very DUMB view as one could say)

  @override
  Widget build(BuildContext context) {
        return MyList(items, selectedItemIdList, (item) { 
            setState((){
               selectedItemIdList.add(item.id);
            });
        });
  }

【讨论】:

  • 您认为与@Unbreachable 的另一个答案相比,您的答案有性能优势吗?
  • 我使用这种方法。我发现 Flutter 在底层代码的绘图和重绘方面比 Android(native)和 IOS 等经典框架要聪明得多。所以我决定只在 Flutter 不够聪明的情况下自己回答这个问题。这意味着我还没有性能问题。换句话说,我没有答案,但我也不关心
猜你喜欢
  • 1970-01-01
  • 2016-07-11
  • 2013-04-27
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 2012-06-18
  • 2020-09-25
  • 2011-07-16
相关资源
最近更新 更多