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
相关产品推荐
相关产品推荐

