如何在 Flutter 中从 ListView 中删除项目

How to remove an item from a ListView in Flutter

提问人:bzc0fq 提问时间:10/18/2023 最后编辑:bzc0fq 更新时间:10/19/2023 访问量:86

问:

我正在寻找一种从 ListView.builder 外部删除 ListView 项的方法。 项目名称和索引是已知的。我试图通过ListView.removeAt(itemIndex)来实现这一点;但不幸的是,这不起作用。

通常。。。有两个 FloatingActionButton:fab1 和 fab2。按下 fab2 后,调用 removeItem(itemName) 函数以从数据库中删除数据(这工作正常),一旦删除数据,应从 ListView 中删除项目(这不起作用,因为我不知道如何引用 ListView)。

在这种情况下,您能否就如何从 ListView 中删除项目提供建议?

完整代码附在下面:

  Future<List<PostListDetails>> postsFuture = getPosts();
  bool showButton = false;
  String itemName ="";
  int itemIndex = 0;


  static Future<List<PostListDetails>> getPosts() async {

    SharedPreferences prefs = await SharedPreferences.getInstance();
    final token = await prefs.getString('token');
    final listID = await prefs.getString('listID');
    final lista = await prefs.getString('lista');

    Response response = await post(Uri.parse('someURL'),
      headers: <String, String>{
        'Accept': 'application/json',
        'Authorization': 'Bearer $token',
      },
      body: <String, String>{
                  'listID': '$listID',
      },
    );

    if(response.statusCode != 200){
      print('Błąd połączenia z bazą danych...  status[${response.statusCode}]');
    }

    final List body = json.decode(response.body);
    return body.map((e) => PostListDetails.fromJson(e)).toList();
  }


void removeItem(String nazwa) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final token = await prefs.getString('token');
    final listID = await prefs.getString('listID');

    try{
      Response response = await post(Uri.parse('SomeURL'),
        headers: <String, String>{
          'Accept': 'application/json',
          'Authorization': 'Bearer $token',
        },
        body: <String, String>{
          'listID': '$listID',
          'nazwa': nazwa,
        },
      );

      if(response.statusCode == 200) {
        //ListView.removeAt(itemIndex);  <--- I do not know how to do this
        
      }else{
        print('Błąd połączenia z bazą danych...  status[${response.statusCode}]');
      }
    }catch(e){
      print(e.toString());
    }
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          FloatingActionButton.extended(
            heroTag: "fab1",
            onPressed: () {
              print('---> addList()');
              // addList();
            },
            icon: const Icon(Icons.add_circle_outline),
            label: const Text('Dodaj'),
          ),
          if (showButton)
            FloatingActionButton.extended(
              heroTag: "fab2",
              onPressed: () {
                print('---> removing $itemName');
                removeItem(itemName);  // this function removes data from database
                setState(() => showButton = false);
              },
              icon: const Icon(Icons.delete),
              label: const Text('Usuń'),
            ),
        ],
      ),
      appBar: AppBar(
        backgroundColor: Colors.grey[900],
        title: const Text('Lista zakupowa'),
        actions: [
          IconButton(
            onPressed: signUserOut,
            icon: const Icon(Icons.logout),
          ),
        ],
      ),
      body: Center(
        child: FutureBuilder<List<PostListDetails>>(
          future: postsFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),
      ),
    );
  }

  @override
  Widget buildPosts(List<PostListDetails> posts) {
    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        int customCompare(PostListDetails a, PostListDetails b) {
          if (a.status != b.status) {
            return a.status!.toInt() - b.status!.toInt();
          }
          final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
          if (categoryCompare != 0) {
            return categoryCompare;
          }
          return a.nazwa.toString().compareTo(b.nazwa.toString());
        }
        return GestureDetector(
          onLongPress: () {
            itemName = post.nazwa!;
            itemIndex = index;
            setState(() => showButton = true);
          },
          child: Container(
            color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
            margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
            height: 50,
            width: double.maxFinite,
            child: Row(
              children: [
                Expanded(
                  flex: 8,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        post.nazwa!,
                        textAlign: TextAlign.left,
                        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: Colors.black),
                      ),
                      Text(
                        '${post.waga!.toString()} g.',
                        textAlign: TextAlign.left,
                        style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: Colors.grey),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  flex: 2,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Expanded(
                        flex: 0,
                        child: Row(),
                      ),
                      Expanded(
                        flex: 10,
                        child: Row(
                          children: [
                            CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
                            Checkbox(
                              value: Int2Bool(post.status!.toInt()),
                              onChanged: (value) {
                                setState(() {
                                  post.status = Bool2Int(value!);
                                });
                                saveStatus(post.nazwa!, post.status!, index);
                                posts.sort(customCompare);
                              },
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

更新 1 我已经更新了源代码并移动了ListView.remove...放入 removeItem 函数。


更新 2

更改后的最终代码如下:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          FloatingActionButton.extended(
            heroTag: "fab1",
            onPressed: () {
              print('---> addList()');
              // addList();
            },
            icon: const Icon(Icons.add_circle_outline),
            label: const Text('Dodaj'),
          ),
          if (showButton)
            FloatingActionButton.extended(
              heroTag: "fab2",
              onPressed: () {
                removeItem(itemName).then((value){
                  setState((){
                    itemToRemove = true;
                  });
                });
                setState(() => showButton = false);
              },
              icon: const Icon(Icons.delete),
              label: const Text('Usuń'),
            ),
        ],
      ),
      appBar: AppBar(
        backgroundColor: Colors.grey[900],
        title: const Text('Lista zakupowa'),
        actions: [
          IconButton(
            onPressed: signUserOut,
            icon: const Icon(Icons.logout),
          ),
        ],
      ),
      body: Center(
        child: FutureBuilder<List<PostListDetails>>(
          future: postsFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),
      ),
    );
  }

  @override
  Widget buildPosts(List<PostListDetails> posts) {
    if (itemToRemove) {
      posts.removeWhere((element) => element.nazwa == itemName);
      itemToRemove=false;
    }
    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        int customCompare(PostListDetails a, PostListDetails b) {
          if (a.status != b.status) {
            return a.status!.toInt() - b.status!.toInt();
          }
          final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
          if (categoryCompare != 0) {
            return categoryCompare;
          }
          return a.nazwa.toString().compareTo(b.nazwa.toString());
        }
        return GestureDetector(
          onLongPress: () {
            itemName = post.nazwa!;
            itemIndex = index;
            setState(() => showButton = true);
          },
          child: Container(
            color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
            margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
            height: 50,
            width: double.maxFinite,
            child: Row(
              children: [
                Expanded(
                  flex: 8,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        post.nazwa!,
                        textAlign: TextAlign.left,
                        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: Colors.black),
                      ),
                      Text(
                        '${post.waga!.toString()} g.',
                        textAlign: TextAlign.left,
                        style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: Colors.grey),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  flex: 2,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Expanded(
                        flex: 0,
                        child: Row(),
                      ),
                      Expanded(
                        flex: 10,
                        child: Row(
                          children: [
                            CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
                            Checkbox(
                              value: Int2Bool(post.status!.toInt()),
                              onChanged: (value) {
                                setState(() {
                                  post.status = Bool2Int(value!);
                                });
                                saveStatus(post.nazwa!, post.status!, index);
                                posts.sort(customCompare);
                              },
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
Android Flutter 列表视图

评论

0赞 tomerpacific 10/18/2023
我看到根据您的代码,小部件是有状态的。因此,当您删除项目列表中的项目时,您需要通过更改状态来使小部件本身重新绘制。由于您没有添加定义有状态小部件的代码,因此很难理解您可能正在做什么或可能不做什么。
0赞 aminjafari-dev 10/18/2023
确保您有正确的索引,打印或调试它以理解,并在另一节中编写 Remove 方法
0赞 bzc0fq 10/18/2023
还行。。。我已经更新了带有更多代码示例的帖子,以便更好地低估......

答:

1赞 Davis 10/18/2023 #1

这可以通过多种方式实现。

  1. 等待项目成功从数据库中删除,然后更新应用程序。这将使用加载器来等待数据库事务,并使用 setState 在完成后更新 UI。

  2. 通过获取列表项的索引并通过更新应用的状态将其删除。posts.removeAt(itemIndex);

我建议你使用方式 1。因为您只需使用 和Future.then()

执行此操作

void removeItem(String nazwa) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final token = await prefs.getString('token');
    final listID = await prefs.getString('listID');

    try{
      Response response = await post(Uri.parse('SomeURL'),
        headers: <String, String>{
          'Accept': 'application/json',
          'Authorization': 'Bearer $token',
        },
        body: <String, String>{
          'listID': '$listID',
          'nazwa': nazwa,
        },
      );

      if(response.statusCode == 200) {
        //ListView.removeAt(itemIndex); Don't need this
       // just refresh UI here
       setState(() { });//<==remove this if you do not want to refresh the entire screen
        
      }else{
        print('Błąd połączenia z bazą danych...  status[${response.statusCode}]');
      }
    }catch(e){
      print(e.toString());
    }
  }

然后,UI会根据DB的变化进行更新

Center(
        child: FutureBuilder<List<PostListDetails>>(
           future: postsFuture,//Replace with getPosts() to directly/actively read from DB 
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),

为了显示正在进行的更改,请使用这样CircularProgressIndicator()

...
bool isLoading = false;
...
FloatingActionButton.extended(
              heroTag: "fab2",
              onPressed: () {
                print('---> removing $itemName');
                //loading
                setState((){
                    isLoading = true;
                  });
                removeItem(itemName).then((value){
                     setState((){
posts.removeWhere((element) => element.itemName == itemName);//<==please confirm that element.itemName is in your model class else correct it
                    isLoading = false;
                  });};  // this function removes data from database
                setState(() => showButton = false);
              },
              icon: const Icon(Icons.delete),
              label: const Text('Usuń'),
            ),

像这样在你的身体里使用它

 body: isLoading ? Center(child: CircularProgressIndicator()) : Center(
        child: FutureBuilder<List<PostListDetails>>(
          future: postsFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasData) {
              return buildPosts(snapshot.data!);
            } else {
              return const Text("Brak danych...");
            }
          },
        ),
      ),

评论

0赞 bzc0fq 10/18/2023
还行。。。我添加了更多代码并将 removeAt 移动到 removeItem 函数中。你能看看这个并提出建议吗......?
0赞 bzc0fq 10/18/2023
哇。。。很多变化...感谢您:)的努力。我以为我会是一行代码的问题......我已经应用了所有更改并且它可以工作,但是这有一个问题。整个列表已更新,而不是一行。你能想象在互联网连接缓慢的情况下会发生什么吗?有没有办法只删除或更新 UI - 一个 ListView 项(一行)而不是所有项(行)来改进这一点?
0赞 Davis 10/18/2023
是的,这是可能的。让我做一些改动
0赞 Davis 10/18/2023
如果这解决了您的问题,请标记为正确
0赞 bzc0fq 10/18/2023
谢谢:) - 我已经接受了你的回答。