Flutter ListView实时过滤失效:ListView.builder无法实时渲染结果求助
Flutter ListView.builder 无法实时渲染搜索结果的解决方案
问题根源
FutureBuilder 重复重置数据
你在FutureBuilder的builder回调中每次都将items和originalSpecialities重新赋值为snapshot.data!,导致每次调用setState(搜索触发时),列表都会被重置为原始数据,过滤结果被覆盖。ModalBottomSheet 上下文隔离
showModalBottomSheet创建的弹窗拥有独立的Widget上下文,外部页面的setState无法触发弹窗内部ListView.builder的重建,因此即使items已被过滤,弹窗列表也不会更新。
修复后的代码
class _UnitsPage extends State<UnitsPage> { late Future<List<UnitModel>> units; late Future<List<Speciality>> specialities; Speciality? _selectedSpeciality; List<Speciality> items = []; List<Speciality> originalSpecialities = []; final TextEditingController _searchSpeciality = TextEditingController(); late Future<Position> position; @override void initState() { super.initState(); units = getUnits(); // 初始化specialities并仅赋值一次原始数据 specialities.then((data) { setState(() { originalSpecialities = data; items = data; }); }); } @override void dispose() { super.dispose(); _searchSpeciality.dispose(); } Future<void> _refreshUnits() async { setState(() { units = getUnits(specialityId: _selectedSpeciality?.id ?? 0); }); } @override Widget build(BuildContext context) { String selectedSpeciality = 'Selecteaza specialitatea'; if (_selectedSpeciality != null) { selectedSpeciality = _selectedSpeciality!.name; } return Scaffold( drawer: const Menu(), appBar: AppBar( systemOverlayStyle: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, statusBarBrightness: Brightness.light, ), flexibleSpace: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xff058efc), Color(0xff74c0fc)], ), ), ), title: Transform.translate( offset: const Offset(-20.0, 0.0), child: const Text('Unități medicale', style: TextStyle(color: Colors.white)), ), iconTheme: const IconThemeData(color: Colors.white), ), body: Padding( padding: const EdgeInsets.fromLTRB(10, 5, 10, 5), child: Column( children: [ // 替换FutureBuilder为条件渲染,避免重复重置数据 if (originalSpecialities.isEmpty) const CircularProgressIndicator() else BootstrapRow( children: [ BootstrapCol( child: TextButton( child: Text(selectedSpeciality), onPressed: () { showModalBottomSheet( isScrollControlled: true, context: context, builder: (context) { // 使用StatefulBuilder让弹窗内部响应状态变化 return StatefulBuilder( builder: (context, setState) { return FractionallySizedBox( heightFactor: 0.8, child: SingleChildScrollView( child: BootstrapRow( children: [ BootstrapCol( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), child: TextField( controller: _searchSpeciality, onChanged: (search) { // 在弹窗内部的setState中更新items setState(() { items = originalSpecialities .where((item) => item.name.toLowerCase().contains(search.toLowerCase())) .toList(); }); }, ), ), ), BootstrapCol( child: SizedBox( height: 700, child: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile( tileColor: (_selectedSpeciality != null && _selectedSpeciality!.id == items[index].id) ? const Color(0xff058efc) : null, textColor: (_selectedSpeciality != null && _selectedSpeciality!.id == items[index].id) ? Colors.white : null, onTap: () { // 更新外部页面的选中状态 this.setState(() { _selectedSpeciality = items[index]; }); Navigator.pop(context); }, title: Text(items[index].name), ); }, ), ), ) ], ), ), ); }, ); }, ); }, ), ), BootstrapCol( child: ElevatedButton( onPressed: _refreshUnits, style: ElevatedButton.styleFrom( padding: const EdgeInsets.all(0), ), child: const Text("Caută"), ), ) ], ), Expanded( child: FutureBuilder<List<UnitModel>>( future: units, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.active: return const Center(child: CircularProgressIndicator(color: Colors.blue)); case ConnectionState.done: if (snapshot.hasData) { return BootstrapRow( children: [ for (UnitModel unit in snapshot.data!) BootstrapCol( xs: 6, child: UnitWidget(unit: unit), ) ], ); } else { return const Text('No data available'); } } }, ), ), ], ), ), ); } }
关键修改说明
原始数据仅初始化一次
在initState中监听specialitiesFuture的完成事件,只给originalSpecialities和items赋值一次原始数据,避免FutureBuilder每次重建时重置列表。用StatefulBuilder包裹弹窗内容
StatefulBuilder可以让弹窗内部拥有独立的状态更新能力,搜索时调用弹窗内部的setState,直接触发ListView.builder的重建,实时渲染过滤结果。简化FutureBuilder逻辑
替换原来的FutureBuilder为条件渲染,仅在原始数据未加载时显示加载指示器,避免不必要的重建。
内容的提问来源于stack exchange,提问作者Alexandru Silasi
相关产品推荐
相关产品推荐

