You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter ListView.builder无法随新数据更新问题求助

问题:搜索过滤球员数据后UI未更新

我实现了一个搜索过滤功能,从Floor数据库获取球员数据。功能逻辑运行正常,日志能看到返回新的球员列表,但UI并未更新,推测是新列表赋值后未触发组件重渲染。请帮忙检查代码是否存在问题:

import 'package:flutter/material.dart';

import '../database/LocalDatabase.dart';
import '../model/Player.dart';

class Pitch extends StatefulWidget {
  const Pitch({super.key});

  @override
  _Pitch createState() => _Pitch();
}

class _Pitch extends State<Pitch> {
  List<Player> playersList = <Player>[];

  Offset positionOne = const Offset(100, 100);
  Offset positionTwo = const Offset(200, 100);

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints.expand(),
      color: Colors.white,
      child: Stack(
        alignment: Alignment.center,
        children: [
          Image.asset("assets/images/pitch.png"),
          Positioned(
            left: positionOne.dx,
            top: positionOne.dy,
            child: Draggable(
              feedback: playerImage(
                  "https://cdn.sofifa.net/players/158/023/22_120.png"),
              childWhenDragging: Opacity(
                opacity: 0,
                child: playerImage(
                    "https://cdn.sofifa.net/players/158/023/22_120.png"),
              ),
              child: playerImage(
                  "https://cdn.sofifa.net/players/158/023/22_120.png"),
              onDragEnd: (details) {
                setState(() {
                  positionOne = details.offset;
                });
              },
            ),
          ),
          Positioned(
            left: positionTwo.dx,
            top: positionTwo.dy,
            child: Draggable(
              feedback: playerImage(
                  "https://cdn.sofifa.com/players/notfound_0_120.png"),
              childWhenDragging: Opacity(
                opacity: 0,
                child: playerImage(
                    "https://cdn.sofifa.com/players/notfound_0_120.png"),
              ),
              child: playerImage(
                  "https://cdn.sofifa.com/players/notfound_0_120.png"),
              onDragEnd: (details) {
                setState(() {
                  positionTwo = details.offset;
                });
              },
            ),
          )
        ],
      ),
    );
  }

  @override
  void initState() {
    super.initState();

    // getPlayers().then((value) {
    //   debugPrint("playerfromdb: ${value[0].name}");
    // });
  }

  Future<List<Player>> getPlayers() async {
    final database =
        await $FloorLocalDatabase.databaseBuilder('local_database.db').build();

    final playerDao = database.playerDao;

    final players = playerDao.getAllPlayers();

    return players;
  }

  Widget playerImage(String imageUrl) {
    return GestureDetector(
        onTap: () => showDialog<void>(
            context: context,
            builder: (BuildContext context) => Dialog(
                backgroundColor: Colors.white,
                child: SizedBox(
                    height: 300,
                    width: 300,
                    child: Column(
                      children: [
                        const SizedBox(height: 24),
                        Container(
                            margin: const EdgeInsets.only(left: 16),
                            height: 48,
                            child: TextField(
                              decoration: const InputDecoration.collapsed(
                                  hintText: 'Enter player name',
                                  focusColor: Colors.transparent),
                              onChanged: (value) {
                                searchPlayers(value);
                              },
                            )),
                        const SizedBox(height: 24),
                        SizedBox(
                          height: 200,
                          child: ListView.builder(
                              itemCount: playersList.length,
                              itemBuilder: (context, index) {
                                return playerItem(playersList.elementAt(index));
                              }),
                        ),
                      ],
                    )))),
        child: SizedBox(
          width: 48,
          height: 48,
          child: Image.network(imageUrl),
        ));
  }

  Widget playerItem(Player? player) {
    return Container(
      height: 48,
      margin: const EdgeInsets.all(8),
      padding: const EdgeInsets.only(left: 8, right: 8),
      decoration: BoxDecoration(
          shape: BoxShape.rectangle,
          color: Colors.white,
          borderRadius: BorderRadius.circular(16),
          boxShadow: const [BoxShadow(blurRadius: 8)]),
      child: Row(
        children: [
          SizedBox(
              height: 36,
              width: 36,
              child: Image.network(player?.playerImageUrl ?? "")),
          const SizedBox(width: 8),
          Text(player?.name ?? "")
        ],
      ),
    );
  }

  Future<void> searchPlayers(String query) async {
    final database = await $FloorLocalDatabase
        .databaseBuilder('local_database.db')
        .build();

    final playerDao = database.playerDao;

    // await List<Player> filteredPlayers =
   playerDao.searchPlayers(query).then((value) {
      setState(() => playersList = value);
      debugPrint(value[0].name);
    });
  }
}

问题分析与解决方案

核心问题:Dialog的上下文独立于当前State

你在playerImage方法中弹出的Dialog,其内部ListView依赖的playersList属于_Pitch的State,但Dialog是独立于_Pitch组件上下文创建的弹窗。当调用setState更新playersList时,只会触发_Pitch组件的重渲染,而已经弹出的Dialog不会跟着更新。

修复方案:将Dialog抽为独立的StatefulWidget

让Dialog自己管理搜索后的球员列表状态,这样搜索时的状态更新能直接触发Dialog内部UI的重渲染:

1. 创建独立的搜索Dialog组件

class PlayerSearchDialog extends StatefulWidget {
  const PlayerSearchDialog({super.key});

  @override
  State<PlayerSearchDialog> createState() => _PlayerSearchDialogState();
}

class _PlayerSearchDialogState extends State<PlayerSearchDialog> {
  List<Player> playersList = <Player>[];

  Future<void> searchPlayers(String query) async {
    final database = await $FloorLocalDatabase
        .databaseBuilder('local_database.db')
        .build();
    final playerDao = database.playerDao;
    
    final filteredPlayers = await playerDao.searchPlayers(query);
    setState(() {
      playersList = filteredPlayers;
    });
    debugPrint(filteredPlayers.isNotEmpty ? filteredPlayers[0].name : "无匹配球员");
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      backgroundColor: Colors.white,
      child: SizedBox(
        height: 300,
        width: 300,
        child: Column(
          children: [
            const SizedBox(height: 24),
            Container(
              margin: const EdgeInsets.only(left: 16),
              height: 48,
              child: TextField(
                decoration: const InputDecoration.collapsed(
                  hintText: 'Enter player name',
                  focusColor: Colors.transparent,
                ),
                onChanged: searchPlayers,
              ),
            ),
            const SizedBox(height: 24),
            SizedBox(
              height: 200,
              child: ListView.builder(
                itemCount: playersList.length,
                itemBuilder: (context, index) {
                  return playerItem(playersList[index]);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget playerItem(Player player) {
    return Container(
      height: 48,
      margin: const EdgeInsets.all(8),
      padding: const EdgeInsets.only(left: 8, right: 8),
      decoration: BoxDecoration(
        shape: BoxShape.rectangle,
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        boxShadow: const [BoxShadow(blurRadius: 8)],
      ),
      child: Row(
        children: [
          SizedBox(
            height: 36,
            width: 36,
            child: Image.network(player.playerImageUrl),
          ),
          const SizedBox(width: 8),
          Text(player.name)
        ],
      ),
    );
  }
}

2. 修改原playerImage方法的Dialog调用

Widget playerImage(String imageUrl) {
  return GestureDetector(
    onTap: () => showDialog<void>(
      context: context,
      builder: (BuildContext context) => const PlayerSearchDialog(),
    ),
    child: SizedBox(
      width: 48,
      height: 48,
      child: Image.network(imageUrl),
    ),
  );
}

额外优化建议

  • 避免重复创建数据库连接:在_Pitch的initState中初始化一次数据库实例,传递给Dialog组件,或者用单例模式管理数据库,减少资源消耗。
  • 添加空状态提示:在ListView中处理playersList为空的情况,显示“暂无匹配球员”之类的提示,提升用户体验。

内容的提问来源于stack exchange,提问作者alfietap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:55:25